本文章所写的代码github地址
Dart是一个在线程中运行的程序,这意味着:如果程序再执行中遇到一个需要长时间执行的操作,程序将会卡住,这不就尴尬了嘛,为了避免这种尴尬的情况,可以使用异步操作使程序在等待一个耗时操作完成时继续处理其他工作。在Dart中,可以使用Future对象来表示异步操作的结果。
Dart的消息循环机制
在进入正题之前,我们先看一下Dart的消息循环机制:
简单总结一下,详细内容可以看文章The Event Loop and Dart
Dart中事件循环的一些主要概念:
- Dart从两个队列执行任务:
event事件队列
和microtask微任务队列
,优先处理microtask微任务队列
; - Dart的方法是不会被其他Dart代码打断的,当main执行完成后,
main isolate
[1]的线程就会去逐一处理消息队列中的消息。 - 事件队列具有来自Dart(Future,Timer,isolate[1] Message等)和系统(用户输入,I/O等);
- 微任务队列目前仅包含来自Dart(这句话我也不太懂);
- 事件循环会优先处理微任务队列,microtask清空之后才开始处理event事件队列。
- 一旦两个队列都为空,则应用程序已经完成工作,并且(取决于其嵌入程序)可以退出。
- main() 函数以及微任务和事件队列中的所有项目都在Dart应用程序的main ioslate[1]上运行。
什么是Future
Future<T>(T表示泛型)表示一个指定类型的异步操作结果(不需要结果的情况可以使用Future<void>),当一个返回Future对象的函数被调用时:
- 将函数放入队列等待执行并返回一个未完成的Future对象
- 当函数执行完成,Future对象中会被赋值执行的结果,已经执行的状态
- 运行状态(pending),白噢是任务还未完成,也没有返回值
完成状态(completed),表示任务已经完成(无论失败还是成功)
例如:
观看程序输出,首先执行完main函数,然后再去执行任务栈中的内容,在该例中也就是我们使用Future加入到event任务栈中,then中的方法会在Future处于完成态(completed)时立马执行,之后我们在详细讲解。
Dart提供了几种Future的创建方法,其中常有的有:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
future.dart中所使用的就是这种方式创建的Future。
其他创建Future的方式包括:
- Future.value() :返回一个指定值的Future
Future.delayed() :返回一个延时执行的Future
两种方式的执行效果
这端代码执行了两个分支:
- main()方法
- event()队列
Future中的任务调度
前面说过:当Future执行完成后,then()注册的回调函数会立即执行,但是then中的函数并不会被添加到事件队列中,只是在事件队列中的任务被执行完成后才被立刻执行(可以理解为:将网络请求放在队列中进行执行,拿到结果后在then中刷新UI)。
首先,人物队列是以FIFO的方式进行,f1,f2,f3依次被加入到任务栈,then()注册的函数并不会被添加到队列,也不会直接运行。当任务栈中的人物被执行后,立刻执行then中的函数,依次类推,可以看到,then中回调函数执行顺序并不取决于注册的顺序,而仅仅与其Future被加入到任务栈的顺序有关。
注意:new Future(()=>null)和new Future(null)有本质的区别,一个函数题为空,什么都不做,一个是参数为空,不存在函数。
稍微修改一下上例的代码:
是否会对结果有所疑惑呢,先看一下
then
的定义吧
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
这里设计到两个关健点:
- 如果Future在then被调用之前已经完成,那么then中的函数会被作为任务添加到microtask队列中;
- then会返回新的Future,并且该Future在
onValue
(then中注册的回调函数)或者onError被执行时就已经处于完成状态了。 -
如果onValue(回调函数)返回值为一个Future,那么then返回的Future将会在onValue返回的future执行完成后处于完成状态
关于后面两点:
其中,每个then都会返回一个新的Future,而该future会在onValue,也就是回调函数执行时处于完成状态,然后立即执行该future的回调函数。
注意,then方法本身会返回一个Future。在then中的函数也返回一个Future,而then所返回的Future会紧跟着函数返回的future之后处于完成状态再执行后续的回调函数。
总结一下:
- 当Future任务完成后,then()注册的回调函数会立即执行。需注意的是,then()注册的函数并不会添加到事件队列中,回调函数只是在事件循环中任务完成后被调用。
- 如果Future在then()被调用之前已经完成计算,那么任务会被添加到微任务队列中,并且该任务会执行then()中注册的回调函数。
- then会返回新的Future,并且该Future在onValue(then中注册的回调函数)或者onError被执行时就已经处于完成状态了。
- 如果onValue(回调函数)返回值为一个Future,那么then返回的Future将会在onValue返回的future执行完成后处于完成状态
如果处理异步操作的结果
包括上面提到的then,有三种方法处理Future的结果:
- then:处理操作执行结果或者错误并返回一个新的Future
- catchError: 注册一个处理错的回调
- whenComplete:类似final,无论错误还是正确,Future执行结束后总是被调用
then中的onError只能处理当前Future中的错误,而catchError能处理整条调用链上的任何错误。
async和await
上面说了Future的基本用法,以及使用Future API处理数据的方法,但是这种方法存在一个问题:使用链式调用的方式把多个future连接在一起,会严重降低代码的可读性。
可以使用async和await关键字实现异步的功能。async和await可以帮助我们像写同步代码一样编写异步代码
**注意:await只能在async函数里出现 **
要想改变异步代码,只需要在函数中添加async关键字
String getAString() {
return "我是一个字符串";
}
## 改写为异步代码
Future<String> getAString() async{
return "我是一个字符串";
}
需要注意的是,在普通函数中,return返回的为T,那么async函数中返回 的是Future<T>。但是并不需要显示的去指定返回的类型,Dart会自动将返回值包装成Future对象。但是,如果原函数返回的为Future<T>,在async函数中返回的仍然是Future<T>,若async函数没有返回值,那么Dart会返回一个null值的Future。
注意观察代码的执行顺序,函数按照顺序执行,首先执行test9函数,接着按照顺序执行firstString()、secondString()、 thirdString().Future.delayed并不会阻碍任何代码的执行,这符合上文中讲的非阻塞任何代码的执行,Future并不会阻塞它所在函数的执行。
我们稍微修改一下代码:
对比两次结果不难发现,async和await关键字使得原本非阻塞式的函数变得同步了,成了阻塞函数了。函数遇到Future,在其未执行完之前一直处于阻塞状态。但是test10函数依旧正常执行。并不会被async函数所阻塞。async和await只会作用当前函数,并不会对其他外部函数造成执行上的影响。
await也可以帮助我们在执行下个语句之前确保当前语句执行完毕: