Dart异步处理机制

Dart异步处理机制(1小时)

        • Dart的异步处理机制概览
        • Dart与事件循环
          • 基本概念
          • 任务调度
            • 任务执行顺序
            • 事件队列
            • 微任务队列
            • 练习测试
          • 多核CPU的利用

Dart的异步处理机制概览

不同操作语言处理耗时任务有不同的处理机制:

  • 多线程,开启一个新的线程,在新线程中进行异步操作,在通过线程间通信,将数据同主线程共享。Java和C++使用这种方式。
  • 单线程+事件循环,JavaScript和Dart就是使用这种方式。
    对于多线程而言,耗时操作会阻塞线程执行,而单线程+事件循环的方式,耗时任务是非阻塞的,下面将具体介绍其工作原理。

Dart与事件循环

基本概念

事件循环并不复杂,Dart维护着一个事件队列(Event Queue),并对事件进行存取操作:

  • ,将需要处理的一系列事件(点击事件、I/O事件、网络事件)放在事件队列中。
  • ,不断在事件队列中取出事件,并执行其对应代码块,直到事件队列清空。
    Dart应用程序在Main Isolate执行main函数之后开始运行,当main函数执行完毕退出后,Main Isolate的线程开始一个个的处理应用程序的事件队列中的内容。下图是简化的流程:
    Dart异步处理机制_第1张图片
    Dart维护着一个事件循环以及两个队列:event事件队列和microtask微任务队列。
  • 事件队列包含所有外部事件,如:I/O、鼠标事件、定时器、Isolate之间的消息等等。
  • 微任务通常来源于Dart内部,微任务队列优先级高于事件队列,且数量非常少。
任务调度
任务执行顺序

在Dart单线程模型中,代码执行顺序如下:

  1. Dart的入口是main函数,所以main函数中的代码会优先执行;
  2. main函数执行完后,会启动一个事件循环(Event Loop),并开始执行队列中的任务;
  3. 按照FIFO(先进先出)的顺序,执行 微任务队列(Microtask Queue)中的所有任务;
  4. 当微任务队列中任务为空时,按照FIFO(先进先出)的顺序,执行 事件队列(Event Queue)中的所有任务;
    Dart异步处理机制_第2张图片

注意:当事件循环正在处理微任务队列的时候。事件队列会被堵塞。这时候APP就无法进行UI绘制,响应鼠标事件和I/O等事件。

虽然可以预测任务执行的顺序,但是我们无法预测事件循环什么时候会从队列中提取任务。Dart事件处理系统基于单线程循环,而不是基于时基(tick,系统的相对时间单位)或者其他的时间度量。例如,当你创建一个延时1s的任务,1s后向事件队列添加一个任务,但在该任务之前的任务结束前,事件循环是不会处理这个任务的,也就是说该任务执行可能是大于1s的。
我们通过Future链指定任务执行顺序:

future.then(...set an important variable...)
  .then((_) {...use the important variable...});

也可以使用Future.whenComplete()来指定任务在最后被调用。

事件队列

可以使用new Future()new Future.delayed()向事件队列种添加事件,这是dart:async中定义的两个Future的构造函数。
值得注意的是,使用new Future.delayed()在延时一定时间后向队列插入一个任务,这个任务想要执行必须满足下面几点:

  1. main方法执行完毕
  2. 微任务队列为空
  3. 该任务前的任务全部执行完
微任务队列

dart:async中定义了scheduleMicrotask()为我们提供创建微任务队列的方法:

scheduleMicrotask(() {
  // ...code goes here...
});

创建一个简单的用于打印的微任务:

import "dart:async";

main(List<String> args) {
  scheduleMicrotask(() {
    print("I am microtask");
  });
}

关于执行顺序,我做了下面几点总结:

  1. 队列执行优先级:main方法 > 微任务队列 > 事件队列,只有优先级高的队列清空,才会执行低优先级任务
  2. 队列任务执行优先级,按照FIFO(先进先出)原则,先入队先执行
  3. 使用Future.delay()延时任务执行,是在执行该语句延时一定时长后在将Future任务插入队列尾,在测试中一般任务是最后执行
  4. Future如果执行完再添加then,该任务会被放入微任务队列中,当前Future执行完后会立刻执行该微任务,执行完该微任务后才执行下一个Future,另外,该微任务如果新建一个Future并没返回,该Future将插入事件队列(除延时任务)的队尾
  5. Future是链式调用,Futurethen 未执行完,下一个then 不会执行。
    为了方便理解,下面提供了些练习~Let’s go!
练习测试

练习一:

import "dart:async";

main(List<String> args) {
  new Future(() => print('future'));
  scheduleMicrotask(() => print('microtask'));
  print('main');
}

运行结果:

main
microtask
future

Dart中事件的执行顺序:main方法 > 微任务队列 > 事件队列,对应总结中的1,2点。

练习二:

import 'dart:async';
main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 2'));

  new Future.delayed(new Duration(seconds:1),
                     () => print('future #1 (delayed)'));
  new Future(() => print('future #2 of 3'));
  new Future(() => print('future #3 of 3'));

  scheduleMicrotask(() => print('microtask #2 of 2'));

  print('main #2 of 2');
}

运行结果:

main #1 of 2
main #2 of 2
microtask #1 of 2
microtask #2 of 2
future #2 of 3
future #3 of 3
future #1 (delayed)

该练习是练习一的加强版,同时对应总结第3点,Future.delay()将事件放入事件队列队尾。
练习三:

import 'dart:async';

main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 3'));

  new Future.delayed(new Duration(seconds:1),
      () => print('future #1 (delayed)'));

  new Future(() => print('future #2 of 4'))
      .then((_) => print('future #2a'))
      .then((_) {
    print('future #2b');
    scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
  })
      .then((_) => print('future #2c'));

  scheduleMicrotask(() => print('microtask #2 of 3'));

  new Future(() => print('future #3 of 4'))
      .then((_) => new Future(
      () => print('future #3a (a new future)')))
      .then((_) => print('future #3b'));

  new Future(() => print('future #4 of 4'));
  scheduleMicrotask(() => print('microtask #3 of 3'));
  print('main #2 of 2');
}

运行结果:

main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #3a (a new future)
future #3b
future #1 (delayed)

首先,根据规则1~3,main()函数先执行,然后微任务执行,然后有延时的事件任务是最后:

main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
.
.
.
future #1 (delayed)

接着事件队列开始执行,根据总结5,链式调用:

future #2 of 4
future #2a
future #2b
future #2c

而在future #2b中新建微任务future #3a (a new future),会在当前Future执行完后执行
接着开始执行future #3, 在其then函数中,新建future #3a并插入队尾,由于future #3a 没执行,所以future #3b也不会马上执行。
这时,队尾情况变为

future #3a (a new future)
future #3b
future #1 (delayed)
之后执行future #4 of 4

需要注意的是,如果把future #3then的代码
.then((_) => new Future( () => print(‘future #3a (a new future)’)))
改为.then((_){ new Future( () => print(‘future #3a (a new future)’));})
因为没有return语句,这时候回调函数返回的是Null Future,future #3a添加到事件队列,但不会阻塞future #3b执行。
执行的结果如下,大家可以体会下其中差别:

main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #3b
future #4 of 4
future #5 of 5
future #3a (a new future)
future #1 (delayed)

练习四

import 'dart:async';
main() {
  print('main #1 of 2');
  Future future1 = new Future(() => print('future #1 of 5'));
  Future future2 = new Future(() =>  null);
  Future future3 = new Future.delayed(Duration(seconds: 1) ,() => print('future #3 of 5'));
  Future future4 = new Future(() => null);
  Future future5 = new Future(() => null);

  future5.then((_) => print('future #5a'));
  future4.then((_) {
    print('future #4a');
    new Future(() => print('future #4b (a new future)'));
    future2.then((_) {
      print('future #2a from future4 #4a');
    });
  });
  future2.then((m) {
    print('future #2b');
  });
  print('main #2 of 2');
}

运行结果:

main #1 of 2
main #2 of 2
future #1 of 5
future #2b
future #4a
future #2a from future4 #4a
future #5a
future #4b (a new future)
future #3 of 5

练习三基本包括了练习四的要点,这里就不详细解释 (主要因为懒 ),主要注意的一点是,future4先于feture5定义,future4的then也会先于future5执行

多核CPU的利用

为了使应用程序保持响应,应该将任务放入Isolate中。Isolate可能运行在一个单独的进程或线程中,这取决于Dart的具体实现。我们已经知道Dart是单线程的,这个线程有自己可以访问的内存空间以及需要运行的事件循环,我们可以将这个空间系统称之为是一个Isolate,比如Flutter中就有一个Root Isolate,负责运行Flutter的代码,比如UI渲染、用户交互等等。

那么应该使用多少Isolate隔离区?
对于计算密集型任务,一般隔离区的数量取决于你CPU有多少可用。

如何创建隔离区?
创建Isolate是比较简单的,我们通过Isolate.spawn就可以创建了:

import "dart:isolate";

main(List<String> args) {
  Isolate.spawn(foo, "I am new Isolate");
}

void foo(info) {
  print("From new isolate:$info");
}

参考:
http://www.cndartlang.com/890.html
https://juejin.im/post/5d36bd3ff265da1b9570995b
https://segmentfault.com/a/1190000020398241

你可能感兴趣的:(Flutter)