Dart是一个在单线程中运行的程序,这意味着:如果程序在执行中遇到一个需要长时间的执行的操作,程序将会被冻结。为了避免造成程序的冻结,可以使用异步操作使程序在等待一个耗时操作完成时继续处理其他工作。在Dart中,可以使用Future
对象来表示异步操作的结果。
Dart的消息循环机制
在进入正题之前,我们先看一下Dart的消息循环机制:
简单总结一下,详细内容可以看文章The Event Loop and Dart
Dart中事件循环的一些主要概念:
- Dart从两个队列执行任务:event事件队列和microtask微任务队列;
- Dart的方法是不会被其他Dart代码打断的,当main执行完成后,main isolate的线程就会去逐一处理消息队列中的消息
- 事件队列具有来自Dart(Future,Timer,Isolate Message等)和系统(用户输入,I/O等);
- 微任务队列目前仅包含来自Dart;
- 事件循环会优先处理微任务队列,microtask清空之后才将event事件队列中的下一个项目出队并处理。
- 一旦两个队列都为空,则应用程序已完成工作,并且(取决于其嵌入程序)可以退出。
- main()函数以及微任务和事件队列中的所有项目都在Dart应用程序的main isolate上运行。
什么是Future
Future<T>表示一个指定类型的异步操作结果(不需要结果可以使用Future<void>)当一个返回 future 对象的函数被调用时:
讲函数放入队列等待执行并返回一个未完成的Future对象
当函数操作执行完成,Future对象变为完成并携带一个值或一个错误
上面两条分别对应两个状态:运行状态(pending),表示任务还未完成,也没有返回值
完成状态(completed),表示任务已经结束(无论失败还是成功)
例如:
# demo1
main() {
Future f1 = new Future(() {
print("我是第一个");
});
f1.then((_) => print("f1 then"));
print("我是main");
}
# print:
# 我是main
# 我是第一个
# f3 then
观察程序输出,首先执行完main函数然后再去执行任务栈中的内容,在该例中也就是我们使用Future假如到event任务栈中的任务<u>,then
中的方法会在Future处于完成态(completed)时立马执行</u>,之后我们再详细讲解。
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;
}
demo1中所使用的就是这种方式创建的Future。
其他创建Future的方式包括:
- Future.value():返回一个指定值的Future
- Future.delayed():返回一个延时执行的Future
main() {
Future.delayed(Duration(milliseconds: 200),(){
print("我是延迟的Future");
});
var future = Future.value("我是Future");
future.then((value) => print(value));
}
# print:
# 我是Future
# 我是延迟的Future
这端代码执行了两个分支:
- main()方法
- event队列
Future中的任务调度
前面讲过:当Future执行完成后,then()注册的回调函数会立即执行,但是then中的函数并不会被添加到事件队列中,只是在事件队列中的任务被执行完成后才被立刻执行(可以理解为:将网络请求放在队列中进行执行,拿到结果后在then中刷新UI)。
main() {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
Future f3 = new Future(() => {print("创建f3")});
f3.then((value) => print("我是f3"));
f2.then((value) => print("我是f2"));
f1.then((value) => print("我是f1"));
}
上面程序的输出结果为:
我是f1
我是f2
创建f3
我是f3
首先,任务栈符合以FIFO的方式运行,f1,f2,f3一次被加入到任务栈,then()注册的函数并不会被添加到队列,也不会直接运行。当任务栈中任务被执行后,立刻运行then中的函数,依次类推。可以看到,then中的回调函数执行的顺序并不取决于注册的顺序,而仅仅与其Future被加入到任务栈的顺序有关。
注意:new Future(() => null)和new Future(null)有本质上的区别,一个函数体为空,什么都不做;一个是参数为空,不存在函数。
稍微修改一下上例中的代码:
main() {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
Future f3 = new Future(() => {print("创建f3")});
f3.then((_) => print("我是f3"));
f2.then((_) {
print("我是f2");
new Future(() => print("我是一个新的"));
f1.then((_) {
print("我是f1");
});
}).then((value) => print("我还是f2"));
}
执行结果为:
我是f2
我还是f2
我是f1
创建f3
我是f3
我是一个新的
先看一下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执行完成后处于完成状态
关于后面两点:
main() async {
Future f2 = new Future(() => null);
f2.then((_) {
print("我是真正的f2");
Future f1 = new Future(() => null);
f1.then((value) => print("我是f1"));
})
.then((value) => print(value))
.then((value) => print("我还是f2吗"));
}
输出结果为:
我是真正的f2
我还是f2吗
我是f1
其中,每个then都会返回一个新的Future,而该future会在onValue
,也就是回调函数执行时处于完成状态,然后立即执行该新future的回调函数。
稍微修改代码:
main() {
Future f2 = new Future(() => null);
f2.then((_) {
print("我是真正的f2");
Future f1 = new Future(() => null);
f1.then((value) => print("我是f1"));
return new Future(() => {print("全新的Future")});
})
.then((value) => print("我还是f2吗"))
.then((value) => print("我不是了"));
}
运行结果为:
我是真正的f2
我是f1
全新的Future
我还是f2吗
我不是了
注意,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能处理整条调用链上的任何错误。
main() async {
Future f1 = new Future(() => null);
Future f2 = new Future(() => null);
f1
.then((value) {
return Future.error("错误了");
})
.then((value) => print("执行成功了吗"), onError: (error) => print(error))
.then((value) => Future.error('错了!'))
.catchError((error) => {print("我也发现:$error")});
f2.then((_) {
print("我是f2");
}).whenComplete(() => print("完成了"));
}
输出结果为:
错误了
我也发现:错了!
我是f2
完成了
async和await
上面讲了Future的基本用法,以及使用Future API处理数据的方法。但是这种方法存在一个问题:使用链式调用的方式把多个future连接在一起,会严重降低代码的可读性。
可以使用async和await关键字实现异步的功能。async和await可以帮助我们像写同步代码一样编写异步代码
main() async {
Future f1 = new Future.delayed(Duration(milliseconds: 2000),() {
return "我是第一个";
});
Future f2 = new Future(() {
return "我是第二个";
});
f2.then((value) => print("哦哦哦"));
print("开始了:${DateTime.now()}");
print("${await f1}:${await f2}");
print("结束了:${DateTime.now()}");
}
输出:
开始了:2020-10-13 15:37:16.871165
哦哦哦
我是第一个:我是第二个
结束了:2020-10-13 15:37:18.877511
注意: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。
main() {
print("main函数开始了");
firstString();
secondString();
thirdString();
print("main函数结束了");
}
firstString() async{
print("firstString函数开始了");
Future.delayed(Duration(milliseconds: 300), () {
return "我是一个字符串";
}).then((value) => {print(value)});
print("firstString函数结束了");
}
secondString() {
print("我是二个字符串");
}
thirdString() {
print("我是三个字符串");
}
上面代码的输出结果为:
main函数开始了
firstString函数开始了
firstString函数结束了
我是二个字符串
我是三个字符串
main函数结束了
我是一个字符串
注意观察代码的执行顺序,函数按照顺序执行,首先执行main函数,接着按照顺序执行firstString()、secondString()thirdString()。Future.delayed并不会阻碍任何代码的执行,这符合上文中讲的非阻塞调用,Future并不会阻塞它所在函数的执行。
我们稍微修改一下代码:
main() {
print("main函数开始了");
firstString();
secondString();
thirdString();
print("main函数结束了");
}
firstString() async {
print("firstString函数开始了");
Future future = Future.delayed(Duration(milliseconds: 300), () {
return "我是一个字符串";
});
print(await future);
print("firstString函数结束了");
}
secondString() {
print("我是二个字符串");
}
thirdString() {
print("我是三个字符串");
}
输出结果为:
main函数开始了
firstString函数开始了
我是二个字符串
我是三个字符串
main函数结束了
我是一个字符串
firstString函数结束了
对比两次结果不难发现,async和await关键字使得原本非阻塞式的函数变的同步了,成了阻塞函数了。函数遇到Future,再其未执行完之前一直处于阻塞状态。但是main函数依旧正常执行,并不会被async函数所阻塞。async和await只会作用于当前函数,并不会对其他外部函数造成执行上的影响。
await也可以帮助我们在执行下个语句之前确保当前语句执行完毕:
main() async {
print("main函数开始了:${DateTime.now()}");
print(await firstString());
print(await secondString());
print(await thirdString());
print("main函数结束了:${DateTime.now()}");
}
firstString() {
return Future.delayed(Duration(milliseconds: 300), () {
return "我是一个字符串";
});
}
secondString() {
return Future.delayed(Duration(milliseconds: 200), () {
return "我是二个字符串";
});
}
thirdString() {
return Future.delayed(Duration(milliseconds: 100), () {
return "我是三个字符串";
});
}
输出结果为:
main函数开始了:2020-10-13 16:24:46.897353
我是一个字符串
我是二个字符串
我是三个字符串
main函数结束了:2020-10-13 16:24:47.527151