我认为有一些关于阻塞的误解。当你看到你的第一个例子时 - await 只会阻止您函数
中的其余代码 执行,而应用程序的其余部分仍将正常工作。
需要理解一件事:async/await 语法只是.then(callback) 语法的语法糖。它们都可以实现相同的功能,只是 async/await 更容易阅读、调试和理解。如您所见 - 在您的两个示例中,您都获得了相同的结果。问题是:您更喜欢哪种语法?
澄清一下 - 假设您想引入几个等待事件,每个事件等待1秒,并在每个事件之后编写消息。
您的第一个示例将如下所示:
import 'dart:async';
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);
}
注意代码的易读性和易理解性。
现在,看第二个例子是如何改变以实现同样的效果的:
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);
});
});
});
}
它们都能实现同样的功能 - 但是第二个例子会让你的眼睛疼。
还有一个有趣的场景需要你考虑 - 如果你想要同时发生几件事情怎么办?这并不罕见 - 如果你需要从3个不同的服务器获取3个图像,你不会按顺序获取它们。你会想要同时发送所有3个请求,并等待它们全部完成。
使用async/await非常容易:
import 'dart:async';
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);
}
请注意,由于我们没有在每个 Future.delayed 前面放置 await,这意味着我们将启动延迟的 future,但我们不会等待它完成。
你会发现整个函数只需要3秒钟才能完成;因为所有三个计时器都在同时运行。Future.wait 将等待一组 futures 完成。
现在 - 很明显,在大多数情况下,您确实不需要 .then() 语法,但我认为它在更复杂的场景中仍然适用。
例如:您需要从3个服务器获取3张图片。每个服务器都有一个备份服务器;如果第一个服务器返回 null 作为结果 - 您需要从备份服务器获取资源。
此外:如果 Backup server 1 或 Backup server 2 返回 null,则需要调用 server 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");
await Future.delayed(Duration(seconds: rng.nextInt(5)));
print("$server is done");
if (rng.nextInt(10)<7) return null;
return 1;
}
void main() async {
Stopwatch watch = Stopwatch();
watch.start();
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");
}
新的一点是: .then() 将返回一个 future 对象 - 您仍然可以使用 await 关键字等待。告诉你它是同样的东西....
如果没有 .then() 语法,您将需要创建一个更多的异步函数来处理此操作,使您的代码变得更加复杂和难以阅读。使用 .then() 语法,代码只是稍微更易于管理。再次看到-.then() 和 async/await 实际上是同样的东西...
标准的 async/await 在线性情况下非常有用(就像我展示的多个 Future.delayed 示例)。但是,当您进入可以通过具有运行在并行的多个分支的图形描述的复杂场景时,.then() 就会派上用场。
编辑 - Dart 是单线程的
关于 Dart 是单线程的,可以这样考虑:您的代码在 Dart 引擎(或 Dart VM)内部运行,而此代码确实是单线程的。但是,任何对外部世界的调用都将并行运行(调用远程服务器,甚至调用本地硬盘,调用同一主机上的其他进程(如操作系统),以及调用类似于我例子中的定时器)。
就像我上面的例子一样:我调用了 3 个远程服务器来获取某些东西,并且我链接了 3 个不同的回调,每个调用一个。而“外部世界”的事情 - 调用服务器 - 真的是并行发生的。Dart 的单线程仅保证在任何给定时间点只执行代码的一行。
如果您来自 Java 背景,则会知道在 Java 中同步多个线程有多么困难:这就是代码通常会出错的地方。在 Dart 中,您无需担心此问题。真正的性能优化是任何在 Dart VM 外发生的事情实际上都在并行运行 - 而 Dart 会为您处理它。
现在这是如何工作的:事件循环。那是一个小的 Dart 引擎,跟踪所有您的远程服务器调用,准备好时回调您的回调过程。事件循环是处理您的代码一次处理一个请求的程序...