Dart异步编程学习札记
Dart是一门单线程,所以在开发中我们不需要像其他多线程语言一样需要考虑资源竞争问题,也没有锁的概念,而Dart编程中如果需要实现等待一个任务完成的同时进行别的任务的操作时,就需要使用异步操作来实现了。
而要在Dart中执行异步操作,则需要使用Future类。
Future的使用
最简单的,我们可以直接这样写:
void main() {
print("任务 1");
Future((){
print("任务 2");
});
print("任务 3");
}
此时控制台打印:
flutter: 任务 1
flutter: 任务 3
flutter: 任务 2
可以看到,虽然任务 2在任务 3之前添加,但是却在任务3执行完毕后才执行。
而且与多线程不同,异步任务不是与同步任务同时进行,而是要等待主线程空闲的时候,才会去执行异步任务。
void main() {
print("任务 1");
Future(() {
print("任务 2");
});
sleep(Duration(seconds: 5));
print("任务 3");
}
执行后:
flutter: 任务 1
flutter: 任务 3
flutter: 任务 2
吐槽一下Dart,单线程语言的性能真心不如多线程语言。。。
如果我们需要在异步操作中修改一个变量的值,并且在后续的同步操作中获取到该值,如:
void main() {
print("任务 1");
Future(() {
_data = "2";
print("任务 2");
});
print("任务 3");
print("data value is: ${_data}");
}
String _data = "0";
显然这样写是无法得到我们想要的结果的,打印结果如下:
flutter: 任务 1
flutter: 任务 3
flutter: data value is: 0
flutter: 任务 2
要解决这个问题,可以使用await和async关键字。
await和async
首先我们将异步操作放到一个函数中, 并且这个函数显然是一个异步函数,因此我们要在函数名后面添加一个async
关键字,表示这是一个异步函数
仅仅一个async关键字是无法满足我们需求的,我们还需要在异步操作前添加一个await
关键字,需要注意的是,await要在异步函数中才能使用。
void asyncTask() async {
await Future((){
_data = "2";
print("任务 2");
});
print("data value is: ${_data}");
}
打印结果为
flutter: 任务 2
flutter: data value is: 2
继续对await关键字探究,如果我们在asyncTask函数中添加任务4,是否会先执行任务4呢?
void asyncTask() async {
await Future((){
_data = "2";
print("任务 2");
});
print("data value is: ${_data}");
print("任务 4");
}
flutter: 任务 2
flutter: data value is: 2
显然任务4最后才执行,而写在main函数中的任务3先执行了,由此我们可以判断,await关键字作用域为所在异步函数内,声明await后,异步函数后续操作都必须等待await修饰的异步操作执行完毕后才能执行。
处理Future结果
查看源文件,我们发现Future((){})
其实是一个工厂构造函数,我们拿到future对象,进行一些操作
Future.then()
这是一个用来注册Future完成是要调用的回调,它会在future完成后立马执行,而在then的回调中有两个参数,分别是:
Future
显而易见,onValue是任务成功的回调,而onError则是事件错误的回调。
另外,onValue回调中带有一个参数,我们可以通过下面的代码看到它的用法:
Future((){
print("任务 2");
return "value success";
}).then((value) {
print("then 任务");
print(value);
});
执行结果:
flutter: 任务 2
flutter: then 任务
flutter: value success
显然,onValue中的value参数,实际上就是Future中return的值。这个我们就可以实现不用await等待,通过then拿到异步操作返回的结果。这种写法,显然比await清晰许多。
继续探究onError:
Future(() {
print("任务 2");
throw Exception("future error");
}).then((value) {
print("then 任务");
print(value);
}, onError: (error) {
print(error);
});
执行后结果:
flutter: 任务 2
flutter: Exception: future error
可以到,如果future中抛出了一个错误,那么我们可以onError回调中处理这个错误,并且onVaule回调将不会执行。
Future.catchError()
很明显,这个回调也是用来处理error的:
Future(() {
print("任务 2");
throw Exception("future error");
}).catchError((error) {
print(error);
});
结果:
flutter: 任务 2
flutter: Exception: future error
另外,如果我们这么写:
final future = Future(() {
throw Exception("error");
});
future.catchError((error) {
print("catchError $error");
});
future.then((value) {
print("then 任务");
}, onError: (e) {
print("onError $e");
});
结果会出现:
flutter: catchError Exception: error
flutter: onError Exception: error
从上面的结果可以看出,onError和catchError是可以同样捕获错误的
我们接着探究:
final future = Future(() {
throw Exception("error");
});
future.catchError((error) {
print("catchError $error");
}).then((value) {
print("then 任务");
}, onError: (e) {
print("onError $e");
});
结果为:
flutter: catchError Exception: error
flutter: then 任务
而如果我们这么写
final future = Future(() {
throw Exception("error");
});
future.then((value) {
print("then 任务");
}, onError: (e) {
print("onError $e");
}).catchError((error) {
print("catchError $error");
});
结果为
flutter: onError Exception: error
因此,我们可以得出结论:future采用链式编程的时候,如果当前future抛出异常,错误信息会由下一个future的onError或catchError接受处理,并且不会继续往下一个future传递。
Future.whenComplete()
和then相似的是,whenComplete也是在Future执行完毕后调用,但不同的是,不管future中是否抛出异常,whenComplete都会执行
Future(() {
print("任务 2");
throw Exception("future error");
}).whenComplete(() {
print("whenComplete");
}).catchError((error) {
print(error);
});
flutter: 任务 2
flutter: whenComplete
flutter: Exception: future error
多个Future的情况
执行以下代码:
Future(() {
print("future 1");
}).then((value) => print("then 1"));
Future(() {
print("future 2");
}).then((value) => print("then 2"));
Future(() {
print("future 3");
}).then((value) => print("then 3"));
执行结果:
flutter: future 1
flutter: then 1
flutter: future 2
flutter: then 2
flutter: future 3
flutter: then 3
可以看到,多个Future的情况,执行顺序是按照添加的顺序执行的,如果Future后有then回调,那么执行当前future执行完后会立马执行then回调,而不是执行下一个Future。
显然,所有Future都是被添加到某种队列中,按照先进先出的方式进行执行。查看官方文档可以知道,确实在Dart中有事件队列(event queue)、微任务队列(microtask queue)两种队列,而Future异步任务,是被添加到事件队列中。
Dart的事件循环(event loop)
Dart中,有两种队列:
1. 事件队列(event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。
2. 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。
在每一次事件循环中,Dart总是先去第一个微任务队列中查询是否有可执行的任务,如果没有,才会处理后续的事件队列
实践一下:
void main() {
print("任务 1");
Future((){
print("任务 2");
});
scheduleMicrotask((){
print("任务 4");
});
print("任务 3");
}
执行结果为
flutter: 任务 1
flutter: 任务 3
flutter: 任务 4
flutter: 任务 2
可以看到,尽管任务2先被添加到事件队列,但是最终的执行结果是微任务队列中的任务4先被执行。
通过以上的解释,我们就可以分析下面代码的打印顺序
Future x1 = Future(() => null);
x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
}).then((value) => print('8'));
Future x = Future(() => print('1'));
x.then((value) {
print('4');
Future(() => print('9'));
}).then((value) => print('10'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
print('5');
- 首先,同步任务 5 肯定是先被执行,
- 因为 7 是在x1的then中才会被添加,所以先执行的是外部已经添加到微任务队列中的3
- 3执行完后微任务队列为空,走到事件对列,此时事件队列中为x1,1,2,所以会先执行x1的任务,而x1中任务为空,会走到then中6,所以下一个打印的是6,
- 而 6 执行完后还有一个then,所以也会立即执行then中的8,
- 此时,7已经被添加到微任务队列中,所以下一个执行的是7,
- 微任务队列又空了,继续执行事件队列下一个任务1,
- 而后执行then中的4,下一步操作中,
- 9也被添加到事件队列中,所以此时事件队列中还有2和9,
- 4打印完后立即执行then中的10,
- 随后再将事件队列中剩余的任务2和9执行完毕,所以最终的打印顺序为:
5,3,6,8,7,1,4,10,2,9
执行看看:
flutter: 5
flutter: 3
flutter: 6
flutter: 8
flutter: 7
flutter: 1
flutter: 4
flutter: 10
flutter: 2
flutter: 9
与分析一致。
总结
尽管Dart是一门单线程线程语言,但我们仍然可以使用异步操作去执行实现一些诸如网络获取数据,写入数据库等耗时操作,避免阻碍正常事件的执行。