Dart 并发

1,179 阅读6分钟

概要

本文将通过 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 执行规则

  1. Future 只是创建了一个 Event,将 Event 插入到了 Event queue 的队尾;
  2. Future 中的 then 方法并没有创建新的 Event 丢到 Event Queue 中,而只是一个普通的函数调用,在 Event 执行完后,立即开始执行;
  3. 当 Future 在 then 方法先已经执行完成了,则会创建一个 Microtask,将该 Microtask 的添加到 Microtask queue 中,并且该 Microtask 将会执行通过 then 方法传入的函数;
  4. 通过 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 是单线程的,是以事件循环驱动的。