Dart中的异步编程——Future、async和await

2020-05-26 16:21:11 浏览数 (1)

首先Dart是一门单线程的语言,那么Dart异步操作对支持,可以使我们在编写Dart程序时可以异步的来执行耗时操作。从而可以在等待一个操作完成的同时进行别的操作以下是一些常见的异步操作:

  • 通过网络获取数据。
  • 写入数据库。
  • 从文件读取数据。

要在Dart中执行异步操作,可以使用Future类和asyncawait关键字。

# Dart的事件循环(event loop)

Dart中,实际上有两种队列:

  1. 事件队列(event queue),包含所有的外来事件:I/Omouse eventsdrawing eventstimersisolate之间的信息传递。
  2. 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。

因为 microtask queue 的优先级高于 event queue ,所以如果 microtask queue有太多的微任务, 那么就可能会霸占住当前的event loop。从而对event queue中的触摸、绘制等外部事件造成阻塞卡顿。

在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。

异步任务我们用的最多的还是优先级更低的 event queueDartevent queue 的任务建立提供了一层封装,就是我们在Dart中经常用到的Future

正常情况下,一个 Future 异步任务的执行是相对简单的:

  1. 声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行。
  2. 当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。

# Future

Future<T> 类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为 Future<void>。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。

# Future基本用法

# Future工厂构造函数

什么是工厂构造函数

工厂构造函数是一种构造函数,与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象。 在Dart中,工厂构造函数的关键字为factory。我们知道,构造函数包含类名构造函数和命名构造方法,在构造方法前加上factory之后变成了工厂构造函数。也就是说factory可以放在类名函数之前,也可以放在命名函数之前。

下面我们通过Future的工厂构造函数,创建一个最简单的Future

可以看到,Future的工厂构造函数接收一个Dart函数作为参数。这个函数没有参数,返回值是FutureOr<T>类型。

从打印结果可以看出,Future不需要结果时,返回的类型是 Future<void>

注意,是先执行的类型判断,后打印的Future内的操作。

# asyncawait

默认的Future是异步运行的。如果想要我们的Future同步执行,可以通过asyncawait关键字:

可以看到,我们的Future已经同步执行了。await会等待Future执行结束后,才会继续执行后面的代码。

关键字asyncawaitDart语言异步支持的一部分。

异步函数即在函数头中包含关键字async的函数。

  • async:用来表示函数是异步的,定义的函数会返回一个Future对象。
  • await:后面跟着一个Future,表示等待该异步任务完成,异步任务完成后才会继续往下执行。await只能出现在异步函数内部。能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。

注意:在Dart中,async/await都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。 在Dart 2.0之前,async函数会立即返回,而无需在async函数体内执行任何代码 所以,如果我们将代码改成下面的这种形式:

当我们使用了async关键字,意味着testFuture函数已经变成了异步函数。

所以会先执行testFuture函数之后的打印。

在执行完打印后,会开始检查microtask queue中是否有任务,若有则执行,直到microtask queue列队为空。因为microtask queue的优先级是最高的。然后再去执行event queue。一般Future创建的事件会插入event queue顺序执行(使用Future.microtask方法例外)。

# Future.value()

创建一个返回指定value值的Future

代码语言:javascript复制
void testFuture() async {
    var future = await Future.value(1);
    print("future value: $future.");
}
testFuture();
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
future value: 1.
# Future.delayed()

创建一个延迟执行的Future:

代码语言:javascript复制
void testFuture() async {
  Future.delayed(Duration(seconds: 2), () {
    print("Future.delayed 2 seconds.");
  });
}

testFuture();
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
Future.delayed 2 seconds.
# Future简单使用
# 处理Future的结果

对于Future来说,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。

请记住,Future的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用

Dart提供了下面三个方法用来处理Future的结果。

代码语言:javascript复制
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());
# Future.then()

用来注册一个Future完成时要调用的回调。如果 Future 有多个then,它们也会按照链接的先后顺序同步执行,同时也会共用一个event loop

代码语言:javascript复制
void testFuture() async {
    Future.value(1).then((value) {
      return Future.value(value   2);
    }).then((value) {
      return Future.value(value   3);
    }).then(print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
6

同时,then()会在 Future函数体执行完毕后立刻执行:

代码语言:javascript复制
void testFuture() async {
     var future = new Future.value('a').then((v1) {
      return new Future.value('$v1 b').then((v2) {
        return new Future.value('$v2 c').then((v3) {
          return new Future.value('$v3 d');
        });
      });
    });
    future.then(print, onError: print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
a b c d

那么问题来了,如果Future已经执行完毕了,我们再来获取到这个Future的引用,然后再继续调用then()方法。那么此时,Dart会如何处理这种情况?对于这种情况,Dart会将后续加入的then()方法体放入microtask queue,尽快执行:

  1. 因为先调用的testFuture()函数,所以先打印future 13
  2. 再执行testFuture()后面的打印。
  3. 开始异步任务执行。
  4. 首先执行优先级最高的microtask queue任务scheduleMicrotask,打印future 12
  5. 然后按照Future的声明顺序再执行,打印future 1.
  6. 然后到了 futureFinish,打印future 2。此时futureFinish已经执行完毕。所以Dart会将后续通过futureFinish 调用的 then方法放入microtask queue。由于microtask queue的优先级最高。因此 futureFinishthen 会最先执行,打印 future 11
  7. 然后继续执行event queue里面的future 3。然后执行 then,打印 future 4。同时在then方法里向microtask queue里添加了一个微任务。因为此时正在执行的是event queue,所以要等到下一个事件循环才能执行。因此后续的 then 继续同步执行,打印 future 6。本次事件循环结束,下一个事件循环取出 future 5 这个微任务,打印 future 5
  8. microtask queue任务执行完毕。继续执行event queue里的任务。打印 future 7。然后执行 then这里需要注意的是:此时的 then 返回的是一个新创建的 Future 。因此这个 then,以及后续的 then 都将被被添加到event queue中了。
  9. 按照顺序继续执行evnet queue里面的任务,打印 future 10
  10. 最后一个事件循环,取出evnet queue里面通过future 7then方法新加入的 future 8,以及后续的 future 9,打印。
  11. 整个过程结束。
# Future.catchError

注册一个回调,来捕捉Futureerror

代码语言:javascript复制
void testFuture() async {
  new Future.error('Future 发生错误啦!').catchError(print, test: (error) {
    return error is String;
  });
}

testFuture();

print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
Future 发生错误啦!
# then中的回调onErrorFuture.catchError

Future.catchError回调只处理原始Future抛出的错误,不能处理回调函数抛出的错误,onError只能处理当前Future的错误:

代码语言:javascript复制
void testFuture() async {
    new Future.error('Future 发生错误啦!').then((_) {
       throw 'new error';
    }).catchError((error) {
      print('error: $error');
      throw 'new error2';
    }).then(print, onError:(error) {
      print("handle new error: $error");
    });
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
error: Future 发生错误啦!
handle new error: new error2
# Future.whenComplete

Future.whenComplete 在Future完成之后总是会调用,不管是错误导致的完成还是正常执行完毕。比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。并且返回一个Future对象:

代码语言:javascript复制
void testFuture() async {
    var random = new Random();
    new Future.delayed(new Duration(seconds: 1), () {
        if (random.nextBool()) {
          return 'Future 正常';
        } else {
          throw 'Future 发生错误啦!';
        }
    }).then(print).catchError(print).whenComplete(() {
        print('Future whenComplete!');
    });
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
Future 发生错误啦!
Future whenComplete!
代码语言:javascript复制
在testFuture()执行之后打印。
Future 正常
Future whenComplete!
# Future高级用法
# Future.timeout

本来Future会在2s后完成,但是timeout声明的是1s后超时,所以1s后Future会抛出TimeoutException

代码语言:javascript复制
void testFuture() async {
    new Future.delayed(new Duration(seconds: 2), () {
      return 1;
    }).timeout(new Duration(seconds:1)).then(print).catchError(print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
TimeoutException after 0:00:01.000000: Future not completed
# Future.foreach

根据某个集合对象,创建一系列的Future。并且会按顺序执行这些Future。例如,根据{1,2,3}创建3个延迟对应秒数的Future。执行结果为1秒后打印1,再过2秒打印2,再过3秒打印3,总时间为6秒:

代码语言:javascript复制
void testFuture() async {
    Future.forEach({1,2,3}, (num){
      return Future.delayed(Duration(seconds: num),(){print("第$num秒执行");});
    });
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
第1秒执行
第2秒执行
第3秒执行
# Future.wait

等待多个Future完成,并收集它们的结果。有两种情况:

  1. 所有Future都有正常结果返回。则Future的返回结果是所有指定Future的结果的集合: void testFuture() async { var future1 = new Future.delayed(new Duration(seconds: 1), () => 1); var future2 = new Future.delayed(new Duration(seconds: 2), () => 2); var future3 = new Future.delayed(new Duration(seconds: 3), () => 3); Future.wait({future1,future2,future3}).then(print).catchError(print); } testFuture(); print("在testFuture()执行之后打印。"); 执行结果: 在testFuture()执行之后打印。 [1, 2, 3]
  2. 其中一个或者几个Future发生错误,产生了error。则Future的返回结果是第一个发生错误的Future的值: void testFuture() async { var future1 = new Future.delayed(new Duration(seconds: 1), () => 1); var future2 = new Future.delayed(new Duration(seconds: 2), () { throw 'Future 发生错误啦!'; }); var future3 = new Future.delayed(new Duration(seconds: 3), () => 3); Future.wait({future1,future2,future3}).then(print).catchError(print); } testFuture(); print("在testFuture()执行之后打印。"); 执行结果: 在testFuture()执行之后打印。 Future 发生错误啦!
# Future.any

返回的是第一个执行完成的Future的结果,不会管这个结果是正确的还是error

代码语言:javascript复制
  void testFuture() async {
    Future
    .any([1, 2, 5].map((delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
    .then(print)
    .catchError(print);
  }
  testFuture();
  
  print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
1
# Future.doWhile

重复性地执行某一个动作,直到返回false或者Future,退出循环,适用于一些需要递归操作的场景:

代码语言:javascript复制
void testFuture() async {
    var random = new Random();
    var totalDelay = 0;
    Future.doWhile(() {
        if (totalDelay > 10) {
          print('total delay: $totalDelay seconds');
          return false;
        }
        var delay = random.nextInt(5)   1;
        totalDelay  = delay;
        return new Future.delayed(new Duration(seconds: delay), () {
          print('waited $delay seconds');
          return true;
        });
    }).then(print).catchError(print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
waited 5 seconds
waited 5 seconds
waited 3 seconds
total delay: 11 seconds
null
waited 4 seconds
waited 3 seconds
total delay: 12 seconds
null
# Future.sync

会同步执行其入参函数,然后调度到microtask queue来完成自己。也就是一个阻塞任务,会阻塞当前代码,sync的任务执行完了,代码才能走到下一行:

代码语言:javascript复制
void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future.sync(() {
        print("Future sync microtask event 2");
    });
    Future((){
        print("Future event 3");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
Future sync microtask event 2
在testFuture()执行之后打印。
microtask event
Future event 1
Future event 3

但是注意,如果这个入参函数返回一个Future

代码语言:javascript复制
void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future.sync(() {
        return Future(() {  print("Future event 2");
      });
    });
    Future((){
        print("Future event 3");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
microtask event
Future event 1
Future event 2
Future event 3
# Future.microtask

创建一个在microtask queue运行的Future。我们知道microtask queue的优先级是比event queue高的。而一般Future是在event queue执行的。所以Future.microtask创建的Future会优先于其他Future执行:

代码语言:javascript复制
void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future((){
        print("Future event 2");
    });
    Future.microtask((){
        print("microtask event");
    });
}
testFuture();
print("在testFuture()执行之后打印。");

执行结果:

代码语言:javascript复制
在testFuture()执行之后打印。
microtask event
Future event 1
Future event 2

# 写在最后

通过这篇文章我们了解了Dart中的事件循环和event queuemicrotask queue之间关系。同时,介绍了一些关于Dart Future的一些基础使用和高级用法,同时穿插了一些使用实例,用来帮助大家更好的来理解Dart中的异步操作。当然,还有一些关于Dart异步编程和多线程的一些知识,这里没有过多的涉及。会在后续的文章来继续给大家来讲解。

# References

  • Asynchronous programming: futures, async, await
  • 源码地址

0 人点赞