Home > database >  In Dart, an async scope of async function makes me confused
In Dart, an async scope of async function makes me confused

Time:08-25

There was a time when I made a code with this structure while making a code.

void main() async {
  print('one');
  first();
  print('five');
}

void first() async {
  print('two');
  await second();
  print('four');
}

Future<void> second() async {
  print('three');
}

Above code's result was

one two three five four

And I was very confused by this result. I thought the await syntax would wait for the second function and output "four" right away.

However, "five" was printed at fourth and "four" was printed at the right after.

Is there anyone who can tell me why "five" is printed at fourth sequence?


Edit 1.

My initial question was ambiguous, so I add a new explanation.

When I executed below code, the output sequence is

one two three four five.

void main() async {
  print('one');
  first();
  print('five');
}

void first() async {
  print('two');
  second();
  print('four');
}

Future<void> second() async {
  print('three');
}

Yeah that output is so reasonable for me, because print('two'), await second() print('four') is in same function's - first() - scope.

But, in the case of await second(), why print('fout') is not executed right after await second()??

Why await statement break the function scope??

This is my real curiosity...

CodePudding user response:

I think where you get confused is that when we call async functions, we run them synchronously until first await:

Note: Although an async function might perform time-consuming operations, it doesn’t wait for those operations. Instead, the async function executes only until it encounters its first await expression (details). Then it returns a Future object, resuming execution only after the await expression completes.

https://dart.dev/guides/language/language-tour#handling-futures

If we go thought the first posted example, it is executed in the following order:

  1. Print "one".
  2. Calls first() and continues the execution inside here.
  3. Print "two".
  4. Calls second() and continues the execution inside here. The await statement is first coming into play when we return from second() which will give us a Future<void> to await on.
  5. Print "three".
  6. second() returns a Future<void> which is generated automatically from the fact that the method is marked async. In this example, the Future are already completed with a value but the later await are still going to move further executing of second() into a new event on an event queue.
  7. We await the Future<void> from second(). Since this is the first await in first() we will now return a Future<void> from first(). Yes, this happens even if the method is marked to return void so by using void you are really just making it "impossible" for the caller to await the Future.
  8. We are not back at main() and continues to run the rest of this method. Which means we print "five".
  9. Since we don't have more code to execute, we check the microtask event queue (and afterwards if nothing is found event-queue) for any events to be executed. These events is added when a e.g. Future gets completed with a value which happen immediately when second() was done.
  10. The first (and only) event to execute are one that continues the execution of first() from the point of await second();. We now print "four".

CodePudding user response:

When you don't await the Future returned from an async function, then any non-async code within that function will be executed immediately and synchronously.

So, when you remove all awaits, the effect is that you don't have any code executing asynchronously - it's the same result as if none of the functions were marked async.

The behavior you saw in the first example was because you did not use await in main, but you did use await in first. This changes the order of execution, such that the code after await in first will be deferred until after all the synchronously executable code has finished executing.

It might be more clear if, instead of separating these into separate methods, we expand the methods such that all of the code is contained within main, and also replace the usage of await with .then().

For your first example:

void main() async {
  print('one'); // Executes immediately
  print('two'); // Executes immediately
  (() async { 
      print('three'); // Executes immediately
  })().then((_) {
    print('four'); // Execution deferred
  });
  print('five'); // Executes immediately
}

Whereas your second example expands straightforwardly:

void main() async {
  print('one');
  // Begin first()
  print('two');
  // Begin second()
  print('three');
  // End second()
  print('four');
  // End first()
  print('five');
}
  • Related