以下内容为自学笔记,若有幸被大神看到,望指正其不准,补充其不足。万分感谢!!!
Dart语言中没有多线程,但有独特的异步。有独特的消息循环和事件队列,还有独特的生成器机制。
所有的 Dart 代码在 isolates 中运行而不是线程。每个 isolate 都有自己的堆内存,并且确保每个 isolate的状态都不能被其他 isolate 访问。
Dart异步编程中的内容:
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
下面的代码!
Dart中,异步方法返回的对象就是一个Future
对象,当一个future执行完之后,它里面的值就可以使用了。
then()
,whenComplete()
,wait()
,catchError()
。。。
和
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);
}
当要求调用很多异步方法,并且要等待所有方法完成后,继续执行,此时使用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表达式。
可以通过new Future.error('异常内容')
方式抛出异常;
可以通过catchError()
来捕获异常;
在异常中whenComplete()
相当于调用finally
。
//-----------------------------抛出异常--------------------------------
main(){
new Future(() => print('start'))
.then((_) => new Future.error("抛出异常"));//抛出异常为捕获,程序终止
}
//打印结果
start
Unhandled exception:
抛出异常
#0 _rootHandleUncaughtError.> (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
抛出异常
在同步下通过throw
抛出的异常,可以通过try-catch
处理。详见Dart语言(一)最后。
fun1() {
throw 'fun1 is error';
}
fun2() {
throw 'fun2 is error';
}
在返回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.> (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()
代码里面的异常。
dart.async
包中提供的如下API
Future
类,可以添加一个事件到事件队列的末尾。即new Future()
就会有一个事件。scheduleMicrotask()
,可添加一个微任务到微任务队列的末尾。main()
**方法进入后,会从上到下依次执行;event
事件时,将其暂放入事件队里内;main()
方法执行完毕后,消息循环开始工作,首先会按照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);
直至把事件队列内的事件都执行完。
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)中的事件;
then()
方法调用前就完成计算,所以内部事件被放到微任务队列做任务(f1–1);事件队列执行完毕后结束。
注意点:
- 尽量使用事件队列,微任务要尽量简单,否则会引起鼠标无反应等。
- 为使应用程序保持响应,避免在事件循环中添加计算密集型代码
- 执行计算密集型代码的时候,另创建Isolate
Dart中生成器有两种类型:
sync*
—>返回Iterable
对象async*
—>返回Stream
对象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
解析:
- 在yield语句中,为current赋的值;
- 调用moveNext()才会跳进getNum()内;
- moveNext()返回的是bool值,当有数据时返回true。
- sycn*和yield成对配合使用。
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
解析: