[关闭]
@TryLoveCatch 2020-05-08T11:25:07.000000Z 字数 7625 阅读 866

Flutter之异步

flutter


https://www.jianshu.com/p/54da18ed1a9e

isolate

在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main isolate。
每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列。
但和普通Thread不同的是,isolate拥有独立的内存,isolate由线程和独立内存构成。正是由于isolate线程之间的内存不共享,所以isolate线程之间并不存在资源抢夺的问题,所以也不需要锁。
�又由于内存是各自独立的,相互之间并不能进行访问。但isolate提供了基于port的消息机制,通过建立通信双方的sendPort和receiveport,进行相互的消息传递,在Dart中叫做消息传递。
isolate看起来其实和进程比较相似,isolate的整体模型其实更像进程,而async、await更像是线程。

消息循环

  • Dart应用程序仅有一个消息循环,以及两个队列:event事件队列和microtask微任务队列。
  • 在Main Isolate执行main函数之后,当main函数执行完毕退出后,Main Isolate的线程开始一个个的处理队列中的内容。所以,当main函数exit后,消息循环开始工作。
  • 事件队列负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件
  • 微任务队列则包含有Dart内部的微任务,主要是通过scheduleMicrotask来调度。

如下图所示:

Dart的事件循环的运行遵循以下规则:

注意第一步里的所有,也就是说在处理事件队列之前,Dart要先把所有的微任务处理完。如果某一时刻微任务队列里有8个微任务,事件队列有2个事件,Dart也会先把这8个微任务全部处理完再从事件队列中取出1个事件处理,之后又会回到微任务队列去看有没有未执行的微任务。

总而言之,就是对微任务队列是一次性全部处理,对于事件队列是一次只处理一个。

根据上面的规则,我们可以预测任务执行的顺序,但是我们无法预测事件循环什么时候会从队列中提取任务。
例如,当你创建一个延迟任务的时候,事件会在你指定的时间插入到队列末尾,但是并不会立即执行,需要排队等待之前的事件都执行完毕,微任务队列同样如此。

需要注意的是,当执行微任务事件时,会阻塞事件t队列的事件执行,这样就会导致渲染、手势响应等事件事件响应延时。为了保证渲染和手势响应,应该尽量将耗时操作放在事件队列中。

原始方法

调用scheduleMicrotask来让代码以微任务的方式异步执行

  1. scheduleMicrotask((){
  2. print('a microtask');
  3. });

调用Timer.run来让代码以Event的方式异步执行

  1. Timer.run((){
  2. print('a event');
  3. });

仅仅使用回调函数来做异步的话很容易陷入“回调地狱(Callback hell)”,为了避免这样的问题,JS引入了Promise。同样的, Dart引入了Future。

Future

Future就是延时操作的一个封装,可以将异步任务封装为Future对象。

要使用Future的话需要引入dart.async

  1. import 'dart:async';
  1. Future(() => print('立刻在Event queue中运行的Future'));
  1. Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中运行的Future'));
  1. Future.microtask(() => print('在Microtask queue里运行的Future'));
  1. Future.sync(() => print('同步运行的Future'));

这里要注意一下:

  • Future.sync()的同步运行指的是回调函数是同步运行的,会立马执行
  • Future.sync()通过then串进来的回调函数是调度到微任务队列异步执行的。

我们看一个例子吧:

https://dartpad.cn/

  1. Future f1 = new Future((){
  2. print("f1");
  3. });
  4. Future f2 = Future.sync(() {
  5. print("f2");
  6. });
  7. print("f1.then()");
  8. f1.then((_) {
  9. print("f1 then");
  10. });
  11. print("f2.then()");
  12. f2.then((_) {
  13. print("f2 then");
  14. });

结果:

  1. f2
  2. f1.then()
  3. f2.then()
  4. f2 then
  5. f1
  6. f1 then

结果:

Future.sync()的回调函数同步执行,并且then之后的会调度到微任务队列中去

  1. Future(()=> throw 'we have a problem')
  2. .then((_)=> print('callback1'))
  3. .then((_)=> print('callback2'))
  4. .catchError((error)=>print('$error'))
  5. .whenComplete(()=> print('whenComplete'));

这里也需要注意一下:

  • then串起来的回调函数会按照链接的顺序依次执行
  • then串起来的回调函数有可能同步也有可能异步
  • catchError可以捕获task和then的异常
  • whenComplete一定会被执行,无论Future是正常执行完毕还是抛出异常

针对上买呢的第二条,我们看下面的注意事项

注意事项

1、当Future里面的函数执行完成后,then()注册的回调函数会立即执行。需注意的是,then()注册的函数并不会添加到事件队列中,回调函数只是在事件循环中任务完成后被调用。也就是说它们是同步执行,而不是被调度异步执行。
2、如果Future在调用then()(仅仅是调用到then()方法,并没有执行then里面的函数)之前已经完成,那么这些回调函数会被调度到微任务队列异步执行
3、Future()和Future.delayed()构造函数并不会立即完成计算。
4、Future.value()构造函数在微任务中完成计算,其他类似第2条。
5、Future.sync()构造函数会立即执行函数,并在微任务中完成计算,其他类似第2条。

为了方便理解1和2,我们先看一个 简单例子:

  1. Future f1 = new Future((){
  2. print("f1");
  3. });
  4. Future f2 = new Future(() {
  5. print("f2");
  6. });
  7. Future f3 = new Future(() {
  8. print("f3");
  9. });
  10. print("f3.then()");
  11. f3.then((_) {
  12. print("f3 then");
  13. });
  14. print("f2.then()");
  15. f2.then((_) {
  16. print("f2 then");
  17. });
  18. print("f1.then()");
  19. f1.then((_) {
  20. print("f1 then");
  21. });

结果:

  1. f3.then()
  2. f2.then()
  3. f1.then()
  4. f1
  5. f1 then
  6. f2
  7. f2 then
  8. f3
  9. f3 then

解析:

0、添加f1、f2、f3依次到事件队列
1、f3.then()、f2.then()、f1.then()先执行了,表明then()函数先执行,当然里面的回调函数是没有执行的
2、f1执行,因为不符合注意事项2,符合注意事项1,所以f1.then回调函数直接执行
3、f2执行,因为不符合注意事项2,符合注意事项1,所以f2.then回调函数直接执行
4、f3执行,因为不符合注意事项2,符合注意事项1,所以f3.then回调函数直接执行

这里你可能有一个疑问:f3.then()在前面,为啥f1先执行的呢?
因为,最开始

  1. Future f1 = new Future((){
  2. print("f1");
  3. });
  4. Future f2 = new Future(() {
  5. print("f2");
  6. });
  7. Future f3 = new Future(() {
  8. print("f3");
  9. });

这里会按顺序,把f1,f2,f3依次添加到事件队列里面,所以肯定是f1先执行的。

我们再看一个复杂的例子:

  1. Future f1 = new Future((){
  2. print("f1");
  3. });
  4. Future f2 = new Future(() {
  5. print("f2");
  6. });
  7. Future f3 = new Future(() {
  8. print("f3");
  9. });
  10. print("f3.then()");
  11. f3.then((_) {
  12. print("f3 then");
  13. });
  14. print("f2.then()");
  15. f2.then((_) {
  16. print("f2 then");
  17. new Future(() => print("new Future befor f1 then"));
  18. print("f1.then()");
  19. f1.then((_) {
  20. print("f1 then");
  21. });
  22. new Future.microtask((){
  23. print("Future.microtask");
  24. }).then((_){
  25. print("Future.microtask then");
  26. });
  27. print("f2 then end");
  28. });

结果:

  1. f3.then()
  2. f2.then()
  3. f1
  4. f2
  5. f2 then
  6. f1.then()
  7. f2 then end
  8. f1 then
  9. Future.microtask
  10. Future.microtask then
  11. f3
  12. f3 then
  13. new Future befor f1 then

解析:

0、f3.then()和f2.then()执行
1、事件队列里面是f1,f2,f3
2、遍历微任务队列,为空,遍历事件队列,f1执行
3、遍历微任务队列,为空,遍历事件队列,f2执行,然后f2.then,因为f2.then()之前已经执行了,所以立即执行f2.then
4、执行f2.then
    4.1、new Future()添加到事件队列尾部
    4.2、f1.then()执行,因为f1先执行了,所以f1.then添加到微任务队列尾部
    4.3、new Future.microtask()添加到微任务队列尾部
    4.4、打印f2 then end
5、遍历微任务队列,执行f1.then
6、因为微任务队列里面还有任务,所以执行new Future.microtask()的任务,然后直接执行new Future.microtask().then
7、微任务队列为空,遍历事件队列,f3执行,f3.then因为是在之前就执行了,所以直接执行,不用添加到微任务队列
8、遍历微任务队列,为空,遍历事件队列,new Future()执行

异常处理

Dart学习笔记(31):Future和异常处理

一般来说,我们使用try-catch代码块捕获异常:

  1. try {
  2. throw new Exception("Oh, error!");
  3. } catch (e) {
  4. logger.log(e);
  5. }

但是,Future会将任务添加到事件队列,因此计算过程中产生的异常并不在当前代码块中,以致try-catch并不能捕获Future中的异常。

  1. try {
  2. /**
  3. * 抛出异常,程序崩溃
  4. * try-catch不能捕获Future异常
  5. */
  6. new Future.error("Oh, error!");
  7. } catch (e) {
  8. logger.log(e);
  9. }

catchError

在Futrue里面,我们通过catchError来捕获异常。

实例一
  1. new Future((){
  2. throw Exception("futrue");
  3. print("f1");
  4. }).then((_) {
  5. print("f1 then");
  6. throw Exception("futrue then");
  7. })
  8. .catchError((error) {
  9. print(error);
  10. });

结果:

  1. Exception: futrue

结论:

  • Future()回调函数里面的异常可以被catchError捕获
  • then并不会执行
实例二
  1. new Future((){
  2. print("f1");
  3. }).then((_) {
  4. print("f1 then");
  5. throw Exception("futrue then");
  6. })
  7. .catchError((error) {
  8. print(error);
  9. });

结果:

  1. f1
  2. f1 then
  3. Exception: futrue then

结论:

  • then回调函数里面的异常可以被catchError捕获

总结

  • catchError可以捕获Future()和then()回调函数里面的异常
  • Future()回调函数发生异常后,then不会被执行

区分异常

如果我们想要区分是Future()的异常还是then()的异常,应该怎么处理呢?
有两种方法:

方法一
  1. new Future((){
  2. // 这一行
  3. throw Exception("futrue");
  4. print("f1");
  5. }).then((_) {
  6. print("f1 then");
  7. throw Exception("futrue then");
  8. }, onError: (error) {
  9. print("onError");
  10. print(error);
  11. })
  12. .catchError((error) {
  13. print("catchError");
  14. print(error);
  15. });

then()增加了onError参数
结果:

  1. onError
  2. Exception: futrue

我们把throw Exception("futrue");给注释了,那么结果如下:

  1. f1
  2. f1 then
  3. catchError
  4. Exception: futrue then

结论:

  • Future()回调函数发生异常,被then的onError捕获了
  • 而then回调函数发生的异常,被catchError捕获了

这个结论,跟参考的那篇文章正好相反。。。

方法二
  1. new Future((){
  2. // 这一行
  3. throw FormatException("futrue");
  4. print("f1");
  5. }).then((_) {
  6. print("f1 then");
  7. throw IntegerDivisionByZeroException();
  8. })
  9. .catchError((error) {
  10. print("catchError1");
  11. print(error);
  12. }, test: (e) => e is FormatException)
  13. .catchError((error) {
  14. print("catchError2");
  15. print(error);
  16. }, test: (e) => e is IntegerDivisionByZeroException);

结果:

  1. catchError1
  2. Exception: futrue then

我们把throw Exception("futrue");给注释了,那么结果如下:

  1. f1
  2. f1 then
  3. catchError2
  4. IntegerDivisionByZeroException

whenComplete

catchError()相当于try-catch的镜像函数的话,那么whenComplete()则等同于finally。
无论是正常返回值还是出现异常,都会执行注册的回调函数
我们以上面的例子为例:

  1. new Future((){
  2. throw Exception("futrue");
  3. print("f1");
  4. }).then((_) {
  5. print("f1 then");
  6. throw Exception();
  7. })
  8. .catchError((error) {
  9. print("catchError");
  10. print(error);
  11. })
  12. .whenComplete((){
  13. print("whenComplete");
  14. });

结果:

  1. catchError
  2. Exception: futrue
  3. whenComplete

async/await

async和await是什么?它们是Dart语言的关键字,可以让你用同步代码的形式写出异步代码。

看一个例子:

  1. foo() async {
  2. print('foo E');
  3. String value = await bar();
  4. print('foo X $value');
  5. }
  6. bar() async {
  7. print("bar E");
  8. return "hello";
  9. }
  10. main() {
  11. print('main E');
  12. foo();
  13. print("main X");
  14. }

执行结果:

  1. main E
  2. foo E
  3. bar E
  4. main X
  5. foo X hello

我们主要来看这个:

  1. foo() async {
  2. print('foo E');
  3. String value = await bar();
  4. print('foo X $value');
  5. }

这个会被分成两部分:

  1. foo() async {
  2. print('foo E');
  3. Future future = bar()
  4. ---------------------------
  5. String value = await future;
  6. print('foo X $value');
  7. }

分割线上面的会先执行,await后面的必须是一个Future,然后,分割线下面的就会以then的方式链入这个Future被异步调度执行。

相当于:

  1. foo() {
  2. print('foo E');
  3. return Future.sync(bar).then((value) => print('foo X $value'));
  4. }
  • async标注的函数其返回值类型是Future
  • await后面必须是Future对象
  • await并不像字面意义上程序运行到这里就停下来啥也不干等待Future完成。而是立刻结束当前函数的执行并返回一个Future。函数内剩余代码通过调度异步执行。
  • await只能在async函数中出现。
  • async函数中可以出现多个await,每遇见一个就返回一个Future, 实际结果类似于用then串起来的回调。
  • async函数也可以没有await, 在函数体同步执行完毕以后返回一个Future,相当于第一条

另外,使用async和await还有一个好处是我们可以用和同步代码相同的try/catch机制来做异常处理。

  1. foo() async {
  2. try {
  3. print('foo E');
  4. var value = await bar();
  5. print('foo X $value');
  6. } catch (e) {
  7. // 同步执行代码中的异常和异步执行代码的异常都会被捕获
  8. } finally {
  9. }
  10. }

参考

Flutter/Dart中的异步
Dart学习笔记(30):Event Loop事件循环

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注