概要
本文将通过 Dart 并发模型和 Dart 并发编程两个方面来介绍 Dart 并发。
Dart 并发模型
Dart 是一种单线程语言
首先,Dart 是单线程的,并且 Flutter 依赖于 Dart。
所谓的单线程,就是 Dart 在同一时刻只能执行一个代码块,其他代码块需要在改代码块执行完成后开始执行。一旦有代码块开始执行,就不会被其他 Dart 代码块中断。如果某个代码块执行比较耗时,那么该代码块执行期间将造成整个应用进程阻塞。
其实 Dart 中没有线程的概念,但是引入了 Isolate 的概念,为了便于理解,本文中提到的线程等价与 Isolate, 在 Dart 中, 每个 Isolate 都是隔离的,它们之间不会共享内存。每一个 Dart 进程都是从 Main Isolate 的 main 函数开始执行的,main 函数会通过 Event loop 中一个接着一 个处理 Event。
Dart 并发模型实现
Main Isolate 启动时会初始化 Event loop 和 Event queue,每一个 Main Isolate 只有一个 Event loop,但是存在两个先进先出的 Event queue:Microtask queue 和 Event queue。
Microtask queue 中的 Event 优先被处理,直到 Microtask queue 队列中的 Event 为空时,才会去执行 Event queue 中的 Event。
Microtask queue
MicroTask queue 用于非常简短且需要异步执行的内部动作,这些动作需要在其他事情完成之后并在将执行权送还给 Event queue 之前运行。
Event Queue
Event queue 主要处理外部事件和 Future。外部事件包括 I/O, 手势,绘图,计时器,流数据。
Dart 并发编程
前面介绍了 Dart 并发模型,那 Dart 应该怎样进行并发编程呢?
Microtask 和 Event
通过向 Microtask queue 和 Event queue 发送事件就可以实现并发编程。
- 可以通过调用 scheduleMicrotask 来让代码块以 Microtask 的方式异步执行:
scheduleMicrotask(() {
print('microtask');
});
- 可以通过调用 Timer.run 来让代码块以 Event 的方式异步执行:
Timer.run(() {
print('event');
});
现在可以实现并发编程了,但是要注意执行的顺序,如果 Microtask queue 不为空,Event queue 中的 Event 永远不会执行,除非特殊的需求,否则推荐使用 Event。
Future
使用上面的 API 实现并发编程很容易陷入到“回调地狱”中,所以 Dart 引入了 Future。
Future 提供了一系列的构造方法:
- 创建一个立刻在事件队列里运行的Future:
Future(() => print('立刻在Event queue中运行的Future'));
- 创建一个延时1秒在事件队列里运行的Future:
Future.delayed(
const Duration(seconds: 1), () => print('延时1秒在Event queue中运行的Future'));
- 创建一个同步在事件队列里运行的Future:
Future.sync(() => print('同步在Event queue中运行的Future'));
- 创建一个在微任务队列里运行的Future:
Future.microtask(() => print('立即在Microtask queue里运行的Future'));
- 创建一个立刻执行的返回值的Future
Future.value('立刻执行的返回值的Future');
Future 也提供了一些成员方法:
- then方法将回调函数串起来,解决回调地狱的问题:
Future(() => print('task'))
.then((_) => print('callback1'))
.then((_) => print('callback2'));
- catchError方法捕捉Future和then方法抛出的异常:
Future(() => throw 'we have a problem')
.then((_) => print('callback1'))
.then((_) => print('callback2'))
.catchError((error) => print('$error'));
- whenComplete方法无论Future和then方法是否抛出异常都会执行:
Future(() => throw 'we have a problem')
.then((_) => print('callback1'))
.then((_) => print('callback2'))
.catchError((error) => print('$error'))
.whenComplete(() => print('whenComplete'));
- asStream方法创建一个包含Future结果的Stream:
Future(() => print('task')).asStream();
- timeout方法为Future设置一个超时时间:
Future(() => print('task')).timeout(const Duration(milliseconds: 300));
Future 执行规则
- Future 只是创建了一个 Event,将 Event 插入到了 Event queue 的队尾;
- Future 中的 then 方法并没有创建新的 Event 丢到 Event Queue 中,而只是一个普通的函数调用,在 Event 执行完后,立即开始执行;
- 当 Future 在 then 方法先已经执行完成了,则会创建一个 Microtask,将该 Microtask 的添加到 Microtask queue 中,并且该 Microtask 将会执行通过 then 方法传入的函数;
- 通过 Future 和 Future.delayed 实例化的 Future 不会同步执行,它们会被调度到事件队列异步执行。
Future 复杂示例
根据上面的 Future 执行规则,练习下面的示例。
void future() {
Future f3 = Future(() => print('f3'));
Future f1 = Future(() => print('f1'));
Future f2 = Future(() => print('f2'));
Future f4 = Future(() => print('f4'));
Future f5 = Future(() => print('f5'));
f3.then((_) => print('f3.then'));
f2.then((_) => print('f2.then'));
f4.then((_) => print('f4.then'));
f5.then((_) {
print('f5.then');
Future(() => print('f5.then -> new Future'));
f1.then((_) {
print('f5.then -> f1.then');
});
});
}
尝试在 main 函数中运行该方法,最终的结果为:
f3
f3.then
f1
f2
f2.then
f4
f4.then
f5
f5.then
f5.then -> f1.then
f5.then -> new Future
取消 Future
Future 是无法被取消的,但 Stream 是可以取消的,通过 asStream 方法将 Future 转化为 Stream,能够实现取消对 Future 的回调。
Future.value('value').then((value) => print('$value'));
将 Future 转化为 Stream
Stream stream = Future.value('value').asStream();
StreamSubscription streamSubscription =
stream.listen((value) => print('$value'));
streamSubscription.cancel();
实际上 Future 也没有真正被取消,只是停止了 Future 的回调,Future 还是会正常的执行,取消 Future 目前是无法实现了。
Completer
虽然无法真正的取消 Future 但是可以使用 Completer 控制 Future 完成的时机。
var completer = Completer();
var future = completer.future;
future.then((value) => print('$value'));
completer.complete('done');
上述代码片段中,创建了一个 Completer,其内部会包含一个 Future。可以在这个 Future 上通过then 方法, catchError 方法和 whenComplete 方法实现回调。持有这个 Completer 实例,可以在合适的时机通过调用 complete 方法即可完成这个 Completer 对应的 Future,并且能够控制 Future 中包含的结果的值。Completer 的控制权完全在我们手中,当然也可以通过调用 completeError 方法以异常的方式结束这个 Future。
async 和 await
原理
async 和 await 是Dart 语言提供的语法糖,可以消除 then 方法回调过多的问题,同时使开发者可以使用同步编程的方式来实现异步编程。
Dart 语言就将其理解为:
- async 方法的返回值是一个 Future;
- async 方法会同步执行该方法的代码直到第一个 await 关键字,await 修饰的方法立即返回一个 Future,async 方法中剩下的的部分会通过 then 方法串联到 Future 上执行。
- await 修饰的方法立即返回一个 Future,并且将 Future 发送到消息队列中;
- async 方法中剩下的的部分会通过 then 方法串联到 Future 上执行,等到 Future 执行完成后将立即执行。
误区
- await 暂停了整个流程直至它执行完成,但事实并非如此,还会继续执行事件循环。
- async 并非并行执行的,也是遵循事件循环处理事件的循序规则执行。
使用
在上面的例子中添加一行代码
Future<void> future() async {
Future f3 = Future(() => print('f3'));
Future f1 = Future(() => print('f1'));
Future f2 = Future(() => print('f2'));
// 添加的代码
await f2;
Future f4 = Future(() => print('f4'));
Future f5 = Future(() => print('f5'));
f3.then((_) => print('f3.then'));
f2.then((_) => print('f2.then'));
f4.then((_) => print('f4.then'));
f5.then((_) {
print('f5.then');
Future(() => print('f5.then -> new Future'));
f1.then((_) {
print('f5.then -> f1.then');
});
});
}
尝试在 main 函数中运行该方法,最终的结果为:
f3
f1
f2
f3.then
f2.then
f4
f4.then
f5
f5.then
f5.then -> f1.then
f5.then -> new Future
await 关键字之前的创建的 Future 会优先执行,之后的代码会在 f2 执行完成后执行。
总结
Dart 是单线程的,是以事件循环驱动的。