1

As I understand it, you can await a future in Dart which blocks the currently executing async function until the future returns a result.

This for instance calling inSequence() takes three second to print its output.

// Return a String after a one second delay. No surprises here.
Future<String> long() async {
  await Future.delayed(Duration(seconds: 1));
  return ":)";
}


void inSequence() async {
  // sequentially wait on the futures - takes 3 seconds
  print(await long());
  print(await long());
  print(await long());
}

Alternatively if you want to run all Futures at once you can call Future.wait([...]). Then all the futures will execute and the result will be available once ready on any subsequent calls to async, so inParallel() below will only take one second to execute.

void inParallel() async {
  var f1 = long();
  var f2 = long();
  var f3 = long();
  
  Future.wait([f1,f2,f3]); // wait on all futures in parallel.
  
  print(await f1);
  print(await f2);
  print(await f3);
}

Everything above makes sense to me. However looking below to confusing() the code does not behave as I would expect. I would expect that this function takes three seconds to execute as each await is awaited before the string is printed, then moving on to the next until the function completes three seconds later. In actuality this function only takes 1 second and prints all results at once. Why? How is this working?

void confusing() async {
  var f1 = long();
  var f2 = long();
  var f3 = long();
  
  print(await f1);
  print(await f2);
  print(await f3);
}

Thanks.

SSMSJM
  • 149
  • 6
  • I am not sure about the details in dart, but I think the difference is the time you start those async functions. In `inSequence` you await the first call to fullfill ilts future and return before you start the next. In `confusing` you still start all of them in parallel. – lupz Jun 08 '22 at 06:45
  • should confusing return Future as well? and you call await confusing() instead – Einzeln Jun 08 '22 at 07:47

1 Answers1

3

Alternatively if you want to run all Futures at once you can call Future.wait([...]). Then all the futures will execute and the result will be available once ready on any subsequent calls to async, so inParallel() below will only take one second to execute.

Not necessarily. Using Future.wait does not itself cause operations to execute in parallel; it just doesn't prevent them from occurring in parallel if they can.1

In actuality this function only takes 1 second and prints all results at once. Why? How is this working?

void confusing() async {
  var f1 = long();
  var f2 = long();
  var f3 = long();

  print(await f1);
  print(await f2);
  print(await f3);
}

You call long three times. Calling long immediately starts executing its body, which calls Future.delayed(Duration(seconds: 1)). long then returns a Future to the caller.

The asynchronous operations corresponding to the f1, f2, and f3 Futures therefore have all already been started, so all of them will complete 1 second later. Note Future.wait does basically the same thing, just that Future.wait operates on an Iterable of Futures instead of a fixed number of them. There isn't anything fundamentally magical about Future.wait.

In contrast, when you do:

void inSequence() async {
  // sequentially wait on the futures - takes 3 seconds
  print(await long());
  print(await long());
  print(await long());
}

you don't call long() (and therefore don't call Future.delayed) until after the previous operation completed, so the asynchronous calls are all serialized.


1 Dart code within an isolate executes in a single thread. If you execute two asynchronous operations, but each operation does all of its work in the main Dart thread, using them with Future.wait will execute them concurrently but not in parallel. For example:

Future<void> spin(Duration duration) async {
  await Future.delayed(Duration.zero);
  var endTime = DateTime.now().add(duration);
  while (DateTime.now().isBefore(endTime)) {}
}

void main() async {
  var oneSecond = const Duration(seconds: 1);
  var stopwatch = Stopwatch()..start();
  await Future.wait([spin(oneSecond), spin(oneSecond)]);
  print(stopwatch.elapsed.inSeconds); // Prints: 2
}

In the above code, spin executes a synchronous loop that occupies the main thread for 1 second, but the loops from both calls cannot execute simultaneously.

However, that doesn't mean that some asynchronous operations can't happen in parallel. In most cases, an asynchronous operation ultimately executes some lower-level operation (such as in the Dart VM/runtime) that is implemented using OS threads. Or maybe you execute a filesystem operation that performs asynchronous I/O. It depends the implementation of the asynchronous operation. If you want to allow asynchronous operations to occur in parallel if possible, you often should be using Future.wait.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • Thank you for your detailed response. I think what it boils down to is that I was thinking that the Future does not start executing until it is awaited, but actually as soon as a Future is returned from an async function it will start executing? or is it as as soon as the future is created? You lost me a bit on the Duration.zero stuff, but i assume that is just busy-work to keep the thread occupied. – SSMSJM Jun 09 '22 at 12:43
  • @SSMSJM Calling an asynchronous function will perform as much work as it can until that function yields execution to the caller with `await` (which is syntactic sugar for returning a `Future` to the caller). Your `long` function creates a delayed `Future` as soon as it executes the `Future.delayed` constructor. The `Future.delayed(Duration.zero)` in my `spin` example is there just to yield execution so that the busy loops don't execute immediately when `spin` is invoked. – jamesdlin Jun 09 '22 at 15:18