async/await/then 在 Dart 中如何真正工作?

moa*_*bly 7 multithreading async-await dart flutter

这可能是一个反复出现的问题,但我发现了相互矛盾的答案,现在我很困惑哪个是正确的。我以为我理解了这个概念,然后我开始阅读所有这些答案并完全感到困惑,所以我正在寻找一个我可以轻松理解的明确简单的答案来解决我的问题。

根据这个答案这篇文章await应该中断代码执行并实际等待未来完成,然后继续按顺序执行其余代码。它还表明这可能会阻塞主线程,这仅在这种情况下才合乎逻辑。

另一方面,来自 flutter 团队的thisthis和this 视频表明,这不会阻止其余的代码执行,它只是注册回调以在 future 完成时执行的语法糖,这与做。await then

现在,我尝试编写一个小程序来了解其中哪一个是正确的,似乎第一种方法是正确的方法:

import 'dart:async';

// prints: 
// 1000+
// 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds); 
  
}
Run Code Online (Sandbox Code Playgroud)

反对:

import 'dart:async';

// prints:
// 0
// 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds);
  
}
Run Code Online (Sandbox Code Playgroud)

所以我只想知道为什么 flutter 团队和一些人建议不await阻止代码执行以及这个概念是如何真正起作用的。

And*_*ija 18

我认为对于阻塞存在一些误解。当您查看第一个示例时,await 将仅阻止函数中其余代码的执行。您的应用程序的其余部分仍然可以正常工作。

您需要了解一件事:async/await 语法只是 .then(callback) 语法的语法糖。它们都实现了相同的目标,只是 async/await 更容易阅读、调试和理解。正如您所看到的 - 在您的两个示例中您都得到了相同的结果。您面临的问题是:您更喜欢哪种语法?

为了澄清 - 让我们假设您想要引入几个 1 秒的等待事件,并在每个事件之后写入一条消息。

您的第一个示例将如下所示:

import 'dart:async';

// prints 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  await Future.delayed(Duration(seconds:1));
  print(watch.elapsedMilliseconds); 

  
}
Run Code Online (Sandbox Code Playgroud)

请注意阅读代码和理解是多么容易。

现在,对第二个示例进行更改以实现相同的效果:

import 'dart:async';

void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){
    print(watch.elapsedMilliseconds);
    Future.delayed(Duration(seconds:1)).then((_){
        print(watch.elapsedMilliseconds);
        Future.delayed(Duration(seconds:1)).then((_){
             print(watch.elapsedMilliseconds);
        });
    });
  });
}
Run Code Online (Sandbox Code Playgroud)

他们都会达到相同的效果 - 但第二个例子会让你的眼睛受伤。

您需要考虑的一个更有趣的场景是 - 如果您希望同时发生几件事情怎么办?这并不罕见 - 如果您需要从 3 个不同的服务器获取 3 个图像,则不会按顺序获取它们。您可能希望同时触发所有 3 个请求,并等待所有请求完成。

使用 async/await 这非常简单:

import 'dart:async';

// prints 1000+
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  var f1 = Future.delayed(Duration(seconds:1));
  var f2 = Future.delayed(Duration(seconds:2));
  var f3 = Future.delayed(Duration(seconds:3));

  await Future.wait([f1, f2, f3]);

  print(watch.elapsedMilliseconds); 

  
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于我们没有将 wait 放在每个 Future.delayed 前面 - 这意味着我们将启动延迟的 future,但我们不会等待它完成。

你会发现整个功能只需要3秒就完成了;因为所有 3 个计时器同时运行。Future.wait 将等待 future 列表完成。

现在 - 很明显,在大多数情况下您实际上并不需要 .then() 语法,但我认为它仍然适用于更复杂的场景。

例如:您需要从 3 个服务器获取 3 个图像。每台服务器都有一个备份服务器;如果第一台服务器返回 null 作为结果 - 您需要从备份服务器获取资源。另外:如果备份服务器1或备份服务器2返回null,则需要调用服务器4来获取单个图像。

您甚至可以绘制一个小图来描述这一点。现在这就是 .then() 语法派上用场的地方 - 我们仍然将它与 async/await 结合起来。我想一旦你完全理解了这个例子 - 你就几乎理解了 async/await 和 .then()。我们走吧:

import 'dart:async';
import 'dart:math';

Future<int?> getImage(String server) async {
  var rng = Random();
  
  print("Downloading from $server");
  
  // we'll add random delay to simulate network
  await Future.delayed(Duration(seconds: rng.nextInt(5)));
  
  print("$server is done");
  
  // high chance of returning null
  if (rng.nextInt(10)<7) return null;
  return 1;
}

// prints 1000+
void main() async {
  
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  // get the image from server 1
  var f1 = getImage("Server 1").then((data) async { 
     return data ?? await getImage("Server 1 backup");
  });
  
  var f2 = getImage("Server 2").then((data) async { 
     return data ?? await getImage("Server 2 backup");
  });

  var f4=Future.wait([f1, f2]).then((data) async {
    if (data[0]==null || data[1]==null) {
       return [await getImage("Server 4")];
    } else {
       return data;
    }
  });
  
  var f3 = getImage("Server 3").then((data) async { 
     return data ?? await getImage("Server 3 backup");
  });

  await Future.wait([f3, f4]);

  print("elapsed ${watch.elapsedMilliseconds} ms"); 
  
}
Run Code Online (Sandbox Code Playgroud)

这里的一件新事情是:.then() 将返回一个 future 对象 - 您仍然可以使用 wait 关键字等待它。告诉过你这是同一件事......

如果没有 .then() 语法,您将需要再创建一个异步函数来处理此问题,从而使您的代码变得有点复杂且更难以阅读。使用 .then() 语法,代码更易于管理。再次看到 - .then() 和 async/await 实际上是同一件事......

当事情呈线性时,标准 async/await 会有所帮助(就像我在多个 Future.delayed 示例中展示的那样)。但是,当您遇到可以通过并行运行多个分支的图来描述的复杂场景时 - .then() 将派上用场。

编辑 - Dart 是单线程

对于 Dart 是单线程的,请这样想:您的代码在 Dart 引擎(或 Dart VM)内运行,并且该代码确实是单线程的。但是对外界的任何调用都将并行运行(调用远程服务器,甚至调用本地硬盘驱动器,调用同一主机上的其他进程(例如操作系统) - 是的,甚至调用计时器,就像我的示例中一样)。

就像上面的例子一样:我调用了 3 个远程服务器来获取一些东西,并且链接了 3 个不同的回调,每个调用 1 个。而“外部世界的事情”——调用服务器——确实是并行发生的。Dart 的单线程只是保证在任何给定时间点只会执行我的一行代码。

如果您有 Java 背景,您就会知道在 Java 中同步多个线程有多么困难:这也是代码经常出错的地方。在 Dart 中,你不需要担心这个。真正的性能优化是这样一个事实:Dart VM 之外发生的任何事情都真正并行运行 - 并且 Dart 会为您处理它。

现在它是如何工作的:事件循环。这是一个小型 dart 引擎,可以跟踪所有远程服务器调用,并在准备好时回调您的回调过程。事件循环是负责让您的代码一次处理一个请求的循环……