首先Dart
是一门单线程的语言,那么Dart
对异步操作
对支持,可以使我们在编写Dart
程序时可以异步的来执行耗时操作。从而可以在等待一个操作完成的同时进行别的操作以下是一些常见的异步操作:
- 通过网络获取数据。
- 写入数据库。
- 从文件读取数据。
要在Dart
中执行异步操作,可以使用Future
类和async
和await
关键字。
# Dart的事件循环(event loop)
在Dart
中,实际上有两种队列:
- 事件队列(
event queue
),包含所有的外来事件:I/O
、mouse events
、drawing events
、timers
、isolate
之间的信息传递。 - 微任务队列(
microtask queue
),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue
,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue
添加的任务主要是由Dart
内部产生。
因为
microtask queue
的优先级高于event queue
,所以如果microtask queue
有太多的微任务, 那么就可能会霸占住当前的event loop
。从而对event queue
中的触摸、绘制等外部事件造成阻塞卡顿。
在每一次事件循环中,Dart
总是先去第一个microtask queue
中查询是否有可执行的任务,如果没有,才会处理后续的event queue
的流程。
异步任务我们用的最多的还是优先级更低的 event queue
。Dart
为 event queue
的任务建立提供了一层封装,就是我们在Dart
中经常用到的Future
。
正常情况下,一个 Future
异步任务的执行是相对简单的:
- 声明一个
Future
时,Dart
会将异步任务的函数执行体放入event queue
,然后立即返回,后续的代码继续同步执行。 - 当同步执行的代码执行完毕后,
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
内的操作。
# async
和await
默认的Future
是异步运行的。如果想要我们的Future
同步执行,可以通过async
和await
关键字:
可以看到,我们的Future
已经同步执行了。await
会等待Future
执行结束后,才会继续执行后面的代码。
关键字async
和await
是Dart
语言异步支持的一部分。
异步函数即在函数头中包含关键字
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
:
void testFuture() async {
var future = await Future.value(1);
print("future value: $future.");
}
testFuture();
print("在testFuture()执行之后打印。");
执行结果:
代码语言:javascript复制在testFuture()执行之后打印。
future value: 1.
# Future.delayed()
创建一个延迟执行的Future
:
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
的结果。
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
。
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
函数体执行完毕后立刻执行:
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
,尽快执行:
- 因为先调用的
testFuture()
函数,所以先打印future 13
。 - 再执行
testFuture()
后面的打印。 - 开始异步任务执行。
- 首先执行优先级最高的
microtask queue
任务scheduleMicrotask
,打印future 12
。 - 然后按照
Future
的声明顺序再执行,打印future 1
. - 然后到了
futureFinish
,打印future 2
。此时futureFinish
已经执行完毕。所以Dart
会将后续通过futureFinish
调用的then
方法放入microtask queue
。由于microtask queue
的优先级最高。因此futureFinish
的then
会最先执行,打印future 11
。 - 然后继续执行
event queue
里面的future 3
。然后执行then
,打印future 4
。同时在then
方法里向microtask queue
里添加了一个微任务。因为此时正在执行的是event queue
,所以要等到下一个事件循环才能执行。因此后续的then
继续同步执行,打印future 6
。本次事件循环结束,下一个事件循环取出future 5
这个微任务,打印future 5
。 microtask queue
任务执行完毕。继续执行event queue
里的任务。打印future 7
。然后执行then
。这里需要注意的是:此时的then
返回的是一个新创建的Future
。因此这个then
,以及后续的then
都将被被添加到event queue
中了。- 按照顺序继续执行
evnet queue
里面的任务,打印future 10
。 - 最后一个事件循环,取出
evnet queue
里面通过future 7
的then
方法新加入的future 8
,以及后续的future 9
,打印。 - 整个过程结束。
# Future.catchError
注册一个回调,来捕捉Future
的error
:
void testFuture() async {
new Future.error('Future 发生错误啦!').catchError(print, test: (error) {
return error is String;
});
}
testFuture();
print("在testFuture()执行之后打印。");
执行结果:
代码语言:javascript复制在testFuture()执行之后打印。
Future 发生错误啦!
# then
中的回调onError
和Future.catchError
Future.catchError
回调只处理原始Future
抛出的错误,不能处理回调函数抛出的错误,onError只能处理当前Future的错误:
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对象:
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
:
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秒:
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
完成,并收集它们的结果。有两种情况:
- 所有
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]
- 其中一个或者几个
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
:
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
的任务执行完了,代码才能走到下一行:
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
:
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
执行:
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 queue
和microtask queue
之间关系。同时,介绍了一些关于Dart Future
的一些基础使用和高级用法,同时穿插了一些使用实例,用来帮助大家更好的来理解Dart
中的异步操作。当然,还有一些关于Dart
异步编程和多线程的一些知识,这里没有过多的涉及。会在后续的文章来继续给大家来讲解。
# References
- Asynchronous programming: futures, async, await
- 源码地址