以下内容为自学笔记,若有幸被大神看到,望指正其不准,补充其不足。万分感谢!!!
一、简介
Dart语言中没有多线程,但有独特的异步。有独特的消息循环和事件队列,还有独特的生成器机制。
所有的 Dart 代码在 isolates 中运行而不是线程。每个 isolate 都有自己的堆内存,并且确保每个 isolate的状态都不能被其他 isolate 访问。
Dart异步编程中的内容:
- 关键字
async
和await
表达式 - future对象
- 任务调度
- 生成器
二、async和await
在异步编程中有以下关键字:
async
:标记的方法即为异步方法。异步方法是一个耗时操作,执行需要一定的时间,但异步方法会立即返回Future
对象。await
表达式:此表达式通常返回一个Future
,如果返回的值不是Future
,则Dart会自把该值放到Future
中返回。await表达式会阻塞住,直到需要的对象返回为止。这里将执行异步的内容。await
可以使用多次,但只能在async
函数中使用。
Future getName1() async {
//此时阻塞住了 会执行getStr1()方法,当await执行完返回future后,将继续执行await下面的代码;
//而此时如果耗时操作则会先打印getName2和getName3
await getStr1();
await getStr2();
print('getName1');
}
getStr1() {
print('getStr1');
}
getStr2() {
print('getStr2');
}
getName2() {
print('getName2');
}
getName3() {
print('getName3');
}
main() {
getName1();//异步方法执行同时,getName2和getName3也会继续执行
getName2();//
getName3();
}
//输出结果
getStr1
getName2
getName3
getStr2
getName1
总结: 这个执行顺序就是Dart异步编程的消息循环和事件队列有关系。
在main
入口方法中代码是按同步顺序执行,当遇到异步方法中的第一个await
的表达式时,main
方法会继续往下执行;而此时这个异步方法中的await
后的表达式也会执行,但这个异步方法会阻塞在这个await
表达式(但不会影响main
方法中的其他代码的执行),直到这个await
表达式执行完并返回数据后,才会继续执行await
下面的代码!
三、Future对象
Dart中,异步方法返回的对象就是一个Future
对象,当一个future执行完之后,它里面的值就可以使用了。
1、Future中常用的方法有
then()
,whenComplete()
,wait()
,catchError()
。。。
2、then()和
whenComplete()方法
返回值都是 Future
对象,可以把多个异步调用串联起来并指定执行顺序;
then()
的回调函数中带有参数,此参数为Future对象内包含的值
main() {
getNum1().then((a) {
//这个回调匿名函数有参数为a和下面的_是一个意思,表示返回值的名字而已
print('a = $a');
//如果下个then要使用这个方法的返回值,要使用return返回,_才能得到数据
return getNum2(a);
}).then((_) {
print('_ = $_');
getResult(_);
});
}
Future getNum1() async {
await println('getNum1', 1);
return 1;
}
Future getNum2(a) async {
await println('getNum2', a + 2);
return a + 2;
}
Future getResult(b) async {
await println('getResult',b + 3);
}
println(i, a) {
print('$i = $a');
}
//打印结果
getNum1 = 1
a = 1
getNum2 = 3
_ = 3
getResult = 6
whenComplete()
方法回调函数中为带参数
main(){
//2.使用whenComplete()按指定顺序,此方法在抛出异常后使用相当于finally,见下面异常
getNum1().whenComplete(() {
//这个匿名方法中没有参数,调用getNum2()时要自己设定参数,目前我还没找到如何获取上一个Future返回是值
getNum2(1).whenComplete((){
getResult(2).then((_) {
print('被执行!');
});
});
});
}
//打印结果
getNum1 = 1
getNum2 = 3
getResult = 5
被执行!
//-----------------这种嵌套使用注意------------------
main(){
//2.使用whenComplete()按指定顺序,此方法在抛出异常后使用相当于finally,见下面异常
getNum1().whenComplete(() {
//这个匿名方法中没有参数,调用getNum2()时要自己设定参数,目前我还没找到如何获取上一个Future返回是值
getNum2(1).whenComplete((){
getResult(2);
});
}).then((_) {
print('被执行!');
});
}
//打印结果
getNum1 = 1
getNum2 = 3
被执行!
getResult = 5
- 在使用上面方式前也可以考虑
await
表达式,使代码更加清晰。
//此代码和then()代码实现相同的功能
goAsync() async{
var a = await getNum1();
var b = await getNum2(a);
await getResult(b);
}
3、wait()方法
当要求调用很多异步方法,并且要等待所有方法完成后,继续执行,此时使用Future.wait()
的静态方法同意管理多个Future
并等待其执行完。
main(){
Future.wait([getNum1(),getNum2(1),getResult(2)])
//此处将这个三个异步方法调用结束后,将Future保存到集合中,getResult
.then((List re) {
//此处打印集合中的Future内容,getResult没有返回语句,默认返回null
re.forEach((i) => print(i));
});
}
//打印结果
getNum1 = 1
getNum2 = 3
getResult = 5
1
3
null
注:
wait()
方法内的异步方法,要有await表达式。
四、异步中的异常
1、Future中的异常
-
可以通过
new Future.error('异常内容')
方式抛出异常; -
可以通过
catchError()
来捕获异常; -
在异常中
whenComplete()
相当于调用finally
。//-----------------------------抛出异常-------------------------------- main(){ new Future(() => print('start')) .then((_) => new Future.error("抛出异常"));//抛出异常为捕获,程序终止 } //打印结果 start Unhandled exception: 抛出异常 #0 _rootHandleUncaughtError.<anonymous closure> (dart:async/zone.dart:1112:29) #1 _microtaskLoop (dart:async/schedule_microtask.dart:41:21) #2 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5) #3 _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:391:30) #4 _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5) #5 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12) //-----------------------------捕获异常-------------------------------- main(){ new Future(() => print('start')) .then((_) => new Future.error("抛出异常")) .catchError((e) => print(e));//将异常捕获,程序执行完 } //打印结果 start 抛出异常 //---------------------------whenComplete()--------------------------- main(){ new Future(() => print('start')) .then((_) => new Future.error("抛出异常")) .whenComplete(() => print("run"))//最后执行语句 .then((_) => print("don\'t")) //未执行 .catchError((e) => print(e)); //捕获语句 } //打印结果 start run 抛出异常
2、同步异常
在同步下通过throw
抛出的异常,可以通过try-catch
处理。详见Dart语言(一)最后。
fun1() {
throw 'fun1 is error';
}
fun2() {
throw 'fun2 is error';
}
3、异步异常与同步异常的混合
在返回Future对象的方法中出现同步异常,会抛出异常的同步方法要放在异步方法内部处理:
Future fun3() {
fun1();
return new Future(() {
fun2();
});
}
main() {
fun3().catchError((e){
print('e = $e');
});
}
//此时在异步中不会捕获成功fun1()
Unhandled exception:
fun1 is error
#0 fun1 (file:///D:/project/flutter_app/lib/demo1.dart:39:3)
#1 fun3 (file:///D:/project/flutter_app/lib/demo1.dart:33:3)
#2 main (file:///D:/project/flutter_app/lib/demo1.dart:28:3)
#3 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:289:19)
#4 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)
//-----------------------处理方式1----------------------------------
Future fun3() {
return new Future.sync(() {
fun1();
return new Future(() {
fun2();
});
});
}
//打印结果
e = fun1 is error
//--------处理方式2-感觉最简单直接--------------------------------------
Future fun3() async{
fun1();
return new Future(() {
fun2();
});
}
//打印结果
e = fun1 is error
注:
then().catchError()
的模式就是异步版本的try-catch
重要:
确保是在then()
返回的 Future 上调用catchError()
,而不是在 原来的 Future 对象上调用。否则的话,catchError()
就只能处理原来 Future 对象抛出的异常而无法处理then()
代码里面的异常。
五、任务调度
1、消息循环和队列
- 在Dart应用程序中,仅有一个消息循环,以及两个队列:event事件队列和microtask微任务队列。
- 事件队列中包含所有的外部事件,如:I/O、鼠标事件、定时器、isolate之间的消息等等。
2、管理队列
dart.async
包中提供的如下API
Future
类,可以添加一个事件到事件队列的末尾。即new Future()
就会有一个事件。- 顶层函数
scheduleMicrotask()
,可添加一个微任务到微任务队列的末尾。
3、调度任务
- Dart语言都是线性执行,当从
main()
方法进入后,会从上到下依次执行; - 当遇
event
事件时,将其暂放入事件队里内; - 当遇到微任务时,将其暂放入微任务队列;
- 当
main()
方法执行完毕后,消息循环开始工作,首先会按照FIFO方式执行微任务队列中所有的微任务; - 当此时的微任务队列执行完毕后,也是按照FIFO(先进先出)的方式一条条执行事件队列中的事件;
- 当执行的事件内有微任务时,会将微任务放到此事件对应的微任务队列,
- 当此事件执行完后,会执行此事件对应的微任务队列中的微任务;
- 当此事件对应的微任务队列执行完毕后,将执行事件队列中的下一个事件。
执行示例图:
代码示例:
main() {
print('同步1');
scheduleMicrotask(() {
print('微任务s1');
new Future(() => print("s1->f"));
});
new Future(() => print('事件f1')).then((_) {
print('事件f1--1');
scheduleMicrotask(() => print('f1->s3'));
scheduleMicrotask(() => print('f1->s4'));
}).then((_) => print('事件f1--2'));
new Future(() => print('事件f2'));
new Future(() => print('事件f3')).then((_) {
print('事件f3--1');
scheduleMicrotask(() {
print('f3->s5');
new Future(() => print("f3->s5->f1"));
});
});
scheduleMicrotask(() => print('微任务s2'));
print('同步2');
}
//打印结果
同步1
同步2
微任务s1
微任务s2
事件f1
事件f1--1
事件f1--2
f1->s3
f1->s4
事件f2
事件f3
事件f3--1
f3->s5
s1->f
f3->s5->f1
图示:
解析:
-
当
main()
方法执行完毕,同步方法1和2被打印;同时生产微任务队列(包含s1和s2)和事件队列(此时只包含f1、f2和f3); -
main()
方法执行完后,按FIFO方式首先执行微任务队列(s1=》s2); 当微任务(s1)内部有事件(s1->f)时,将此事件添加到事件队列末尾(此时队列包含f1、f2、f3、s1->f)。
-
执行完微任务队列后,按FIFO方式再执行事件队列(f1=》f2=》f3=》s1->f);
- 当事件(f1和f3)内部含有微任务时,执行完当前事件后,立即执行此事件内部的微任务(f1->s3,f1->s4和f3->s5);
- 当事件内的微任务(f3->s5)内还有事件(f3->s5->f1)时,将此事件添加到事件队列末尾(此时队列包含f1、f2、f3、s1->f,f3->s5->f1)。
-
直至把事件队列内的事件都执行完。
4、Future的注意事项
Future
中的then()
内注册的函数不会添加到事件队列,只是一个回调函数,它只是在事件循环中任务完成后被调用;- 当
future
类完成计算后,then()
注册的回调函数会立即执行; - 如果
future
在then()
被调用之前已经完成计算,那么任务会被添加到微任务队列中,并且该任务会执行then()
中注册的回调函数; Future()
和Future.delady()
构造函数不会立即完成计算;Future.value()
构造函数在微任务中完成,其他类似第3条;Future.sync()
构造函数会立即执行函数,并在微任务中完成任务,其他类似第3条。
代码示例:
main() {
print('1');
scheduleMicrotask(() => print('s1'));
//此时并未完成计算 ,第二次循环后计算
Future f1 = new Future(() => print('f1'));
Future f2 = new Future(() => print('f2'));
Future f3 = new Future(() => print('f3'));
new Future.sync(() {//同步中立即执行构造方法
print('sync1');
}).then((_) => print('sync1--then'));//然后放到微任务中
f3.then((_) => print("f3--1"));
f2.then((_) {
print("f2--2");
new Future(() => print("匿名f1--3"));
f1.then((_) => print("f1--1"));//此时放到微任务中
});
//统一第二次循环计算,但执行还要在指定延迟后执行
new Future.delayed(new Duration(seconds: 1), () => print('d1'));
scheduleMicrotask(() => print('s2'));
new Future.sync(() => print('sync2')).then((_) => print('sync2->then'));
print('2');
}
//打印结果
1
sync1
sync2
2
s1
sync1--then
s2
sync2->then
f1
f2
f2--2
f1--1
f3
f3--1
匿名f1--3
d1
图例:
解析:
-
当
main()
方法执行完后,会把同步方法执行完,即途中①后面的; -
执行完同步方法后,按FIFO方式先执行微任务队列中的任务;
Future.sync()
方法在构造方法立即执行,将任务(sync1–then和sync2–then)放到微任务队列中; -
执行完微任务队列后,按FIFO方式执行事件队列(f1、f2、f3、d1)中的事件;
- f1在
then()
方法调用前就完成计算,所以内部事件被放到微任务队列做任务(f1–1); - f2内部中有一个匿名事件,此时会将此匿名事件添加到事件队列队尾(f1、f2、f3、d1、匿名f1-3);
- f2执行时,会执行内部的f1的微任务(f1-1),
- d1是延时事件,会在1分钟后执行
- 最后执行匿名f1-1。
- f1在
-
事件队列执行完毕后结束。
注意点:
- 尽量使用事件队列,微任务要尽量简单,否则会引起鼠标无反应等。
- 为使应用程序保持响应,避免在事件循环中添加计算密集型代码
- 执行计算密集型代码的时候,另创建Isolate
六、生成器
Dart中生成器有两种类型:
- 同步生成器:
sync*
—>返回Iterable
对象 - 异步生成器:
async*
—>返回Stream
对象
1、同步生成器:sync*
main() {
//调用getNun立即返回Iterable
var it = getNum(3).iterator; // 1
//调用moveNext方法时getNum才开始执行
while(it.moveNext()) { //2
print(it.current); //3
}
print('over'); //4
}
Iterable getNum(n) sync* { //5
print("Begin"); //6
int k = 0; //7
while (k < n) { //8
//moveNext会返回true给调用者。
//函数会在下次调用moveNext的时候恢复执行。
yield k++; //9
}
print("End"); //10
}
//打印结果
Begin
0
1
2
End
over
执行顺序:1->2->5->6->7->8->9->3->2->8->9->3->2->8->9->3->2->8->10->4
解析:
- 执行1,调用
getNum()
立即返回一个Itrtable,此时getNum()
内部并没有执行;getNum().iterator
返回一个iterator对象; - 执行2,在执行循环时,调用
it.moveNext()
(此时指针指向第一个位)时,才会进入getNum()内部开始执行; - 此时跳入
getNum()
中执行,执行顺序5->6->7->8,此时while()
循环等待; - 执行9,此时会做k++操作,并且断开,不继续往下执行,等待下一次
moveNext()
,然后执行3打印; - 执行2,while循环内执行
it.moveNext()
,每当调用it.moveNext()
就会返回到等待的yield
语句; - 再执行8->9,以此循环3->2->8->9->3->2->8直到循环结束;
- 执行10->4,完成。
- 在yield语句中,为current赋的值;
- 调用moveNext()才会跳进getNum()内;
- moveNext()返回的是bool值,当有数据时返回true。
- sycn*和yield成对配合使用。
2、异步生成器:async*
main() {
//调用getNum立即返回Stream,只有执行了listen,函数才会开始执行
var numStream = getNum(3);//1
numStream.listen((v) {//2
print(v);//3
});
print('over'); //4
}
Stream getNum(n) async* {
print("Begin"); //5
int k = 0; //6
while (k < n) { //7
//不用暂停,数据流通过StreamSubscription进行控制
yield k++; //8
}
print("End");//9
}
//打印结果
over
Begin
0
1
2
End
执行顺序:1->2->4->5->6-> 7->8->2->3->7->8->2->3->7->8->2->3->7 ->9
解析:
- 执行1,此时立即返回一个
Stream
对象,但不会执行getNum()
内的方法;numStream
对象类似于Future在异步中操作; - 执行2,注册监听;
- 执行4,将同步方法执行完,再去执行异步,此时开始执行
getNum()
内部; - 执行5->6->7->8,当执行到yield语句时,将触发监听,将执行2,执行完监听函数3,继续完成while循环;
- 继续执行7->8->2->3->7->8->2->3->7,到循环完毕
- 执行9结束。