关于多线程与异步:
很多人容易把多线程和异步搞混,实际上这是两个概念。
多线程是开辟另外一个线程来处理事件,每个线程都有单独的事件队列,互不影响,这个新线程和主线程是并列执行的,只不过共享数据空间。
而异步是不阻塞当前线程,将异步任务和当前线程的任务分开,异步任务后面的任务,不会等待异步任务执行完再执行,而是直接执行,与异步任务的回调没有因果关系,从而不影响当前线程的执行。
对于开发者来说dart是一门单线程语言,我们在开发中,可以使用异步操作,来执行耗时操作(比如数据请求,文件读写等),通过Future类和async和await关键字实现异步编程。
Dart的事件循环(event loop)
在Dart中,实际上有两种队列:
1.事件队列(event queue
),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate
之间的信息传递。
2.微任务队列(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 async与await
Future:默认的Future是异步运行的,也就是把任务放在Future函数体中,这个函数题会被异步执行
async
:异步函数标识,一般与await
和Future配合使用,如果一个函数是异步的
await
:等待异步结果返回,一般加载Future函数体之前,表明后面的代码要等这个Future函数体内的内容执行完在执行,实现同步执行。
单独给函数添加async
关键字, 没有意义,函数是否是异步的,主要看Future
Future的使用
Future<T>
类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为Future<void>
。也就是说首先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使用
处理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()执行之后打印。");
执行结果:
在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()执行之后打印。");
执行结果:
在testFuture()执行之后打印。
a b c d
那么问题来了,如果Future已经执行完毕了,我们再来获取到这个Future的引用,然后再继续调用then()方法。那么此时,Dart会如何处理这种情况?对于这种情况,Dart会将后续加入的then()方法体放入microtask queue,尽快执行:
1、因为先调用的testFuture()函数,所以先打印future 13。
2、再执行testFuture()后面的打印。
3、开始异步任务执行。
4、首先执行优先级最高的microtask queue任务scheduleMicrotask,打印future 12。
5、然后按照Future的声明顺序再执行,打印future 1.
6、然后到了 futureFinish,打印future 2。此时futureFinish已经执行完毕。所以Dart会将后续通过futureFinish 调用的 then方法放入microtask queue。由于microtask queue的优先级最高。因此 futureFinish 的 then 会最先执行,打印 future 11。
7、然后继续执行event queue里面的future 3。然后执行 then,打印 future 4。8、同时在then方法里向microtask queue里添加了一个微任务。因为此时正在执行的是event queue,所以要等到下一个事件循环才能执行。因此后续的 then 继续同步执行,打印 future 6。本次事件循环结束,下一个事件循环取出 future 5 这个微任务,打印 future 5。
9、microtask queue任务执行完毕。继续执行event queue里的任务。打印 future 7。然后执行 then。这里需要注意的是:此时的 then 返回的是一个新创建的 Future 。因此这个 then,以及后续的 then 都将被被添加到event queue中了。
10、按照顺序继续执行evnet queue里面的任务,打印 future 10。
11、最后一个事件循环,取出evnet queue里面通过future 7的then方法新加入的 future 8,以及后续的 future 9,打印。
12、整个过程结束。
Future.catchError
注册一个回调,来捕捉Future的error:
void testFuture() async {
new Future.error('Future 发生错误啦!').catchError(print, test: (error) {
return error is String;
});
}
testFuture();
print("在testFuture()执行之后打印。");
执行结果:
在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()执行之后打印。");
执行结果:
在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()执行之后打印。");
执行结果:
在testFuture()执行之后打印。
Future 发生错误啦!
Future whenComplete!
在testFuture()执行之后打印。
Future 正常
Future whenComplete!
Future高级用法
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 发生错误啦!
多线程
flutter是单线程的,但是留有多线程的操作方法
Dart中一般使用Isolate实现多线程。Isolate有独立的内存空间,不用考虑多线程时资源抢夺问题,即不需要锁。所以Isolate更像多进程操作。但是进程之间的通信也就相对麻烦,其使用port通信。
int a = 10;
Future<void> dartIsolate() async {
print('开始');
print("外部 a = ${a}");
Isolate.spawn(funcA,100);
//创建Port
ReceivePort port = ReceivePort();
//创建Isolate
Isolate ISO = await Isolate.spawn(funcB,port.sendPort);
port.listen((message) {
print("portMessage ${message}");
a = message;
//关闭端口
port.close();
//销毁ISO
ISO.kill();
});
print('结束');
print("外部 a = ${a}");
}
void funcA(int p) {
a = p;
print('funcA');
print("内部 a = ${a}");//a = 100 说明两个a不在同一片内存空间,间接说明 Isolate其实是开辟一个新的进程
}
void funcB(SendPort sendPort) {
print('funcB');
sendPort.send(1000);
}
结果:
flutter: 开始_name = dingding
flutter: 开始
flutter: 外部 a = 10
flutter: 结束_name = dingding
flutter: funcA
flutter: 内部 a = 100
flutter: funcB
flutter: 结束
flutter: 外部 a = 10
flutter: portMessage 1000
更轻的compute
compute 是对Isolate的封装,更轻量的。线程通信更加方便
//compute 是对Isolate的封装,更轻量的。线程通信更加方便
void computeTest() async {
print("start");
//compute能直接拿到回调,不需要做多余的进程端口通信
int x = await compute(funcC,10);
print('x = ${x}');
print("end");
}
int funcC(int p) {
print('funcC');
return p + 100;
}
打印:
flutter: start
flutter: funcC
flutter: x = 110
flutter: end