Flutter - Dart中的异步编程及多线程
前言
生命在于不断学习,探索未知的世界!!!
今天我们将从Future出发,一步一步探索Dart中的异步编程及相关的多线程知识。之前看网上一些文章说Dart没有多线程,那到底是不是这样呢?今天我们一探究竟!
本文涉及关键词:
- Dart的事件循环
- Future 、async和await等
- isolate
- compute
Dart的事件循环
在Dart
中,实际上有两种队列:
- 事件队列(
event queue
):包括所有的外来事件,I/O
、mouse events
、drawing events
、timers
、isolate
之间的信息传递。 - 微任务队列(
microtask queue
):表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue
,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue
添加的任务主要是由Dart
内部产生。
怎么理解这个呢?我们可以借鉴下面这张图看看Dart
的事件循环所谓的优先级
是怎样的一个过程。
上图可见,在每一次事件循环中,
Dart
总是先去第一个microtask queue
中查询是否有可执行的任务,如果没有,才会处理后续的event queue
的流程。
在我们日常开发用的多的还是优先级低的事件队列
,Dart
为 event queue
的任务建立提供了一层封装,就是我们在Dart
中经常用到的Future
。
正常情况下,一个 Future
异步任务的执行是相对简单的:
-
声明一个
Future
时,Dart
会将异步任务的函数执行体放入event queue
,然后立即返回,后续的代码继续同步执行
。 -
当同步执行的代码执行完毕后,
event queue
会按照加入event queue
的顺序(即声明顺序
),依次取出事件,最后同步执行Future
的函数体及后续的操作。
Future 、async和await
首先我们可以引入一个简单的例子,来看看这个Future
的基本使用:
void main() {
getData();
print('做其他事情');
}
String _data = '0';
void getData() {
print('开始data = $_data');
Future(() {
//耗时操作
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
});
print('结束data=$_data');
}
下面是运行打印结果:
Future异步执行
可以看出,在当前线程中,Future
所包含的任务是异步执行的,先进行Future
之后的任务,再执行Future
内的函数,如果我们想要同步执行的话,也就是先执行Future
内的函数再执行之后的事件,可用通过关键字async
和await
:
void main() {
getData();
print('做其他事情');
}
String _data = '0';
void getData() async {
print('开始data = $_data');
await Future(() {
//耗时操作
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
});
print('结束data=$_data');
}
Future同步执行
可以看到,此时的Future
所包含的任务就是同步执行,await
会等待Future
执行结束后,才会继续执行后面的代码。而且在添加async
和 await
后,当前getData
函数直接返回了,立刻就执行了main
函数内后续任务,所以可见添加了async的函数对当前线程后续操作是没有阻拦效果的,提高程序运行效率。
此时,在添加async
函数之后,getData
这整个函数已经是个异步函数,getData
会返回一个Future
对象。
异步函数即在函数头中包含关键字
async
的函数。
-
async
: 用来表示函数是异步的,定义的函数会返回一个Future
对象。 -
await
:后面跟着一个Future
,表示等待该异步任务完成,异步任务完成后才会继续往下执行。await
只能出现在异步函数内部(await
单独存在是没有意义的,要配合async
使用)。能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。
Future.then()
我们先来看看这个then()的用法:
void main() {
getData();
print('做其他事情');
}
String _data = '0';
void getData() {
print('开始data = $_data');
Future future = Future(() {
//耗时操作
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
});
future.then((value) => print('结束data=$_data'));
print('再多干点事情');
}
then使用
因为
Future
返回的是一个Future
对象,获取这个对象,调用then
方法,我们发现then
里面的任务是在Future
任务块结束后执行的。不妨再多加两个then方法的调用:
future.then((value) => print('结束data=$_data'));
future.then((value) => print('结束1data=$_data'));
future.then((value) => print('结束2data=$_data'));
多个then方法使用
由此可知,then方法是用来注册一个Future完成时要调用的回调。如果 Future 有多个then,它们也会按照链接的先后顺序同步执行,同时也会共用一个event loop。
查看then方法源码可知then方法最终返回也是一个Future对象:
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
也就是说then方法可以链式调用:
future.then((value) => print('结束data=$_data'))
.then((value) => print('结束1data=$_data'))
.then((value) => print('结束2data=$_data'));
打印结果和上面一样一样的。
综合型例子
下面我们利用一个综合型例子来看事件队列、微任务队列及then方法之间的关系:
void testFuture() {
Future x1 = Future(() => null);
Future x2 = x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
});
x2.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 3 6 8 7 1 4 10 2 9
分析:
- 首先调用
testFuture()
函数,所以优先打印5
。 - 执行优先级较高的微任务事件
3
。 - 然后按照Future的声明顺序执行,打印
6
。此时只是将7
这个微任务添加到队列中,暂时还没轮到7
执行。 - 打印完
6
后,继续立即执行x2.then
方法,所以打印8
。 - 打印完
8
之后,此时队列里有添加进来的优先级高的7
,所以打印7
。 - 然后继续向下执行,打印
1
,接着4
,接着就是10
。同样,此处的9
只是添加进event queue
,并不执行。 - 你以为此时会打印刚添加进去的
9
,不不不!要知道任务2
是在一开始打印5
之前就添加进了event queue
, 所以2
应该在9
之前,10
之后打印。9
才是最后被添加进事件队列的,所以最后打印。
结论
-
then()
函数是前面Future
函数体执行结束后立即执行的,可以看作是一个微任务
,优先级高。 - 微任务队列(
microtask queue
)优先级高于事件队列(event queue
)。
Future.catchError()
顾名思义,catchError()是用来捕捉Future抛出的错误的。
String _data = '0';
void getData() {
print('开始data = $_data');
Future future = Future(() {
//耗时操作
for (int i = 0; i < 1000000000; i++) {}
throw '网络错误';
});
future
.then((value) => print('结束data=$value'))
.catchError((e) => print('捕获到了:' + e.toString()));
print('再多干点事情');
}
catchError使用
链式调用
catchError()
函数,最终捕获到了原始Future
所抛出的错误,而我们可以看到此时then()
函数暂未打印,说明一旦原Future
跑出错误,then()
不会执行。
注意点
:catchError()尽量放在链式调用最后面。
then中的回调onError 和 Future.catchError
Future.catchError
回调只处理原始Future
抛出的错误,不能处理回调函数抛出的错误,onError
只能处理当前Future
的错误:
future.catchError((e) => print('捕获到了:' + e.toString()))
.then((value) => print('结束data=$value'),onError: (e) => print('then出错误了'));
catchError放前面使用
future.then((value) => print('结束data=$value'),
onError: (e) => print('then出错误了'))
.catchError((e) => print('捕获到了:' + e.toString()));
catchError放后面使用
catchError源码:
Future<T> catchError(Function onError, {bool test(Object error)?});
上面两个例子可以看出:
-
catchError()
捕获原Future
抛出错误时,then()
里的onError
就不执行了,因为catchError
源码可知,catchError()
返回的也是一个Future
,且返回的Future
无错误抛出,所以当调用then
函数时,里面的onError
就无错误可处理,不打印。 - 而当原
Future
先调用then()
时,此时onError
捕获到了原Future
抛出的错误,并处理打印。然后返回的Future
无错误抛出,所以最后的catchError
无错误处理,暂不打印。
Future.whenComplete()
Future.whenComplete()
在Future
完成之后总是会调用,不管是抛出错误还是正常执行结束。
future
.then((value) => print('结束data=$value'))
.whenComplete(() => print('完成了'))
.catchError((e) => print('捕获到了:' + e.toString()));
whenComplete放前面
future
.then((value) => print('结束data=$value'))
.catchError((e) => print('捕获到了:' + e.toString()))
.whenComplete(() => print('完成了'));
whenComplete放后面
所以,在Future
链式调用中,只要前面的Future
完成,whenComplete
就会执行。即使是下面这种情况:
future
.whenComplete(() => print('完成了'))
.then((value) => print('结束data=$value'))
.catchError((e) => print('捕获到了:' + e.toString()));
whenComplete放原Future后面
所以,只要当前的Future
函数执行体结束,whenComplete
就会执行。
Future.wait()
wait()
我们只简单介绍下用法:
void waitDemo() {
Future.wait([
Future(() {
print('1 执行');
return '任务1 ';
}),
Future(() {
print('2 执行');
return '任务2 ';
}),
Future(() {
print('3 执行');
return '任务3 ';
})
]).then((value) => print(value[0] + value[1] + value[2]));
}
wait()使用
简单理解就是,wait()
中的Future
执行体依次先执行,然后最后再通过then
去处理所有的Future返回的结果。
Dart中的多线程
首先我们从一个简单Future
异步任务入手:
void isolateDemo() {
print('1');
Future(func);
sleep(Duration(seconds: 2));
print('2');
}
func() => print('3');
Future异步
Future
是个异步任务,所以func
的执行一定需要等待2任务
结束再执行,因为都在一个主线程里。下面我们引入Isolate()
:
void isolateDemo() {
print('1');
Isolate.spawn(func, 10);
sleep(Duration(seconds: 2));
print('2');
}
func(int count) => print('3');
Isolate使用
看到没,打印顺序变成了
1 3 2
,说明3
任务没有被2
任务所阻拦,也就是说3
任务不在主队列里面执行,而是在子线程
里执行。我们再把代码复杂点:
void isolateDemo() {
print('1');
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
Isolate.spawn(func, 10);
Isolate.spawn(func1, 10);
sleep(Duration(seconds: 2));
print('2');
}
func(int count) => print('第一个来了');
func1(int count) => print('第二个来了');
Isolate使用2
打印结果可见,
func()
和 func1()
两个函数执行顺序是随机的,也即是说随机开辟了子线程去执行。Isolate
可以结合端口(port
)使用:
void isolateDemo() async {
print('1');
//创建一个port
ReceivePort port = ReceivePort();
//创建Isolate
Isolate iso = await Isolate.spawn(func, port.sendPort);
port.listen((message) {
a = message;
print('接收到了$a');
port.close();
iso.kill();
});
sleep(Duration(seconds: 2));
print('a = $a');
}
int a = 10;
func(SendPort send) {
send.send(100);
}
可实现在子线程中修改变量,但是主线程中的变量还是不变。
注意:
端口记得关闭port.close()
,子线程需要手动杀死iso.kill()
。
Dart中的Compute()
Dart
中还有另外一个Compute
可以调起子线程:
void computeDemo() {
print('1');
compute(func2, 10);
sleep(Duration(seconds: 2));
print('2');
}
func2(int count) => print('3');
Compute使用
打印结果可见,
3
任务同样也是在子线程
中进行的。
异步结合多线程使用
void computeFuncDemo() {
Future(() => compute(funcC, 123)).then((value) => print('1结束'));
Future(() => compute(funcC, 123)).then((value) => print('2结束'));
Future(() => compute(funcC, 123)).then((value) => print('3结束'));
Future(() => compute(funcC, 123)).then((value) => print('4结束'));
Future(() => compute(funcC, 123)).then((value) => print('5结束'));
}
funcC(int message) {}
异步多线程1
此时的异步任务中开启了子线程,
=>
符号的意思是返回,所以then()
的调用也是在子线程中进行。我们再改一下代码:
void computeFuncDemo() {
Future(() {
compute(funcC, 123);
}).then((value) => print('1结束'));
Future(() {
compute(funcC, 123);
}).then((value) => print('2结束'));
Future(() {
compute(funcC, 123);
}).then((value) => print('3结束'));
Future(() {
compute(funcC, 123);
}).then((value) => print('4结束'));
Future(() {
compute(funcC, 123);
}).then((value) => print('5结束'));
}
funcC(int message) {}
异步多线程2
改过的代码居然顺序执行,这不是偶然,因为此时的
then
的调用者是Future
本身,此处没有=>
符号里的返回的意思,所以此时按声明顺序打印。当在所有
compute
之前加上return
,结果又不一样了:异步多线程3
网友评论