0
votes

This is the code that uses Future

void main(List<String> arguments) {
  testFuture();
  print('main');
}

Future testFuture() {
  return Future(() {
    for (var i = 0; i < 1000000000; i++) {}
    print('futureDone');
  });
}

The output is as follows

main
futureDone

When I want to use async to simplify my code, the code looks like this

Future testFuture() async{
  for (var i = 0; i < 1000000000; i++) {}
  print('futureDone');
}

The output is as follows

futureDone
main

At first I was confused, I don't know why they output is different, I am looking for a lot of information, until I saw these words on the https://dart.dev/codelabs/async-await#execution-flow-with-async-and-await

An async function runs synchronously until the first await keyword. This means that within an async function body, all synchronous code before the first await keyword executes immediately.

I don't know why,So with that in mind, if I want to use async I need to write code like this.

Future testFuture() async{
  await Future.delayed(Duration(seconds: 0));
  for (var i = 0; i < 1000000000; i++) {}
  print('futureDone');
}

The output is as follows

main
futureDone

By reading the StackFlow problem in older versions of Dart, I found that the old version didn't "An async function runs synchronously until the first await the keyword. This means that within An async function body, All synchronous code before the first await keyword Executes immediately. ", why did DART make such a change? Or is there something wrong with the way I use it?

2
There are always a cost when changing context. It makes sense you want to execute as much as possible instead of just putting an event on the event queue which we are going to execute immediately. E.g. the first code in the async method is most likely to be calling some code where we want to wait for the result. And maybe, there are a possibility that we don't always ends up await on something (e.g. if we implement some kind of cache).julemand101

2 Answers

4
votes

The change was made because the old behavior, of delaying the start of an async function, was getting in the way of people doing what they wanted to do.

Say you want to get the first ten elements of a stream. You'd write that as:

Future<List<T>> getTen<T>(Stream<T> stream) async {
  var list = <T>[];
  await for (var event in stream) {
    list.add(event);
    if (list.length == 10) break;
  }
  return list;
}

That looks perfectly fine. However, if the stream is a broadcast stream, and the async function delayed the first part of the code, then you wouldn't listen to the stream immediately, and might miss some events.

Another issue is things like reentrancy checking. If you want to prevent calling a function twice, you can do:

Result maybeDoFoo() {
  if (_mayDoFoo) return _doFoo();
  throw "Can't do foo!";
}
Result _doFoo() {
  mayDoFoo = false;
  var result = doTheActualFoo();
  mayDoFoo = true;
  return result;
}

Here the code guards you against calling doFoo while it's already executing. Then you try to make the function async, perhaps because doTheActualFoo needs to depend on something which is already async, and you write:

Future<Result> maybeDoFoo() async {
  if (_mayDoFoo) return _doFoo();
  throw "Can't do foo!";
}
Future<Result> _doFoo() async {
  mayDoFoo = false;
  var result = doTheActualAsyncFoo();
  mayDoFoo = true;
  return result;
}

If the initial part of the async body is delayed, then there is an async gap between the if (mayDoFoo) and the mayDoFoo = false, which means that you can call doFoo twice, both seeing mayDoFoo as true, and then slightly later both of them enter the _doFoo function. That breaks the security of this pattern completely. Such code was found in the wild.

The behavior of async functions was changed to run immediately to avoid mistakes like this. It was too unpredictable in its original behavior, and code patterns which worked well in synchronous code would become subtly fragile when directly ported to being async. With that change, delays only happen at awaits, not anywhere else, making it more predictable what's actually going on. (Well, almost, because async* functions are still delayed from the call to listen on its stream, so they can lose broadcast stream events.)

0
votes

LRN answer code example

This is the old version of the code

void main(List<String> args) {
  maybeDoFoo().then((value) => print(value));
  maybeDoFoo().then((value) => print(value));
}
bool mayDoFoo = true;
Future maybeDoFoo() async{
  await Future.delayed(Duration(seconds: 0));
  if (mayDoFoo) return _doFoo();
  return("Can't do foo!");
}
Future _doFoo() async{
  await Future.delayed(Duration(seconds: 0));
  mayDoFoo = false;
  await Future.delayed(Duration(seconds: 5));
  var result = 1;
  mayDoFoo = true;
  return result;
}

print:

1

1

now

void main(List<String> args) {

  maybeDoFoo().then((value) => print(value));
  maybeDoFoo().then((value) => print(value));
}
bool mayDoFoo = true;
Future maybeDoFoo() async{
  // await Future.delayed(Duration(seconds: 0));
  if (mayDoFoo) return _doFoo();
  return("Can't do foo!");
}
Future _doFoo() async{
  // await Future.delayed(Duration(seconds: 0));
  mayDoFoo = false;
  await Future.delayed(Duration(seconds: 5));
  var result = 1;
  mayDoFoo = true;
  return result;
}

print:

Can't do foo!

1