Dart语言(三)--异步编程

以下内容为自学笔记,若有幸被大神看到,望指正其不准,补充其不足。万分感谢!!!

一、简介

Dart语言中没有多线程,但有独特的异步。有独特的消息循环和事件队列,还有独特的生成器机制。

所有的 Dart 代码在 isolates 中运行而不是线程。每个 isolate 都有自己的堆内存,并且确保每个 isolate的状态都不能被其他 isolate 访问。

Dart异步编程中的内容:

  1. 关键字asyncawait表达式
  2. future对象
  3. 任务调度
  4. 生成器

二、async和await

在异步编程中有以下关键字:

  1. async:标记的方法即为异步方法。异步方法是一个耗时操作,执行需要一定的时间,但异步方法会立即返回Future对象。
  2. await表达式:此表达式通常返回一个Future,如果返回的值不是Future,则Dart会自把该值放到Future中返回。await表达式会阻塞住,直到需要的对象返回为止。这里将执行异步的内容。
  3. 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 对象,可以把多个异步调用串联起来并指定执行顺序;

  1. 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
  1. 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
  1. 在使用上面方式前也可以考虑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中的异常

  1. 可以通过new Future.error('异常内容')方式抛出异常;

  2. 可以通过catchError()来捕获异常;

  3. 在异常中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、消息循环和队列

  1. 在Dart应用程序中,仅有一个消息循环,以及两个队列:event事件队列和microtask微任务队列。
  2. 事件队列中包含所有的外部事件,如:I/O、鼠标事件、定时器、isolate之间的消息等等。

2、管理队列

dart.async包中提供的如下API

  • Future类,可以添加一个事件到事件队列的末尾。即new Future()就会有一个事件。
  • 顶层函数scheduleMicrotask(),可添加一个微任务到微任务队列的末尾。

3、调度任务

  1. Dart语言都是线性执行,当从 main() 方法进入后,会从上到下依次执行;
  2. 当遇event事件时,将其暂放入事件队里内;
  3. 当遇到微任务时,将其暂放入微任务队列;
  4. main()方法执行完毕后,消息循环开始工作,首先会按照FIFO方式执行微任务队列中所有的微任务;
  5. 此时的微任务队列执行完毕后,也是按照FIFO(先进先出)的方式一条条执行事件队列中的事件;
    1. 当执行的事件内有微任务时,会将微任务放到此事件对应的微任务队列,
    2. 当此事件执行完后,会执行此事件对应的微任务队列中的微任务;
    3. 当此事件对应的微任务队列执行完毕后,将执行事件队列中的下一个事件。

执行示例图:
在这里插入图片描述

代码示例:


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

图示:
在这里插入图片描述
解析:

  1. main()方法执行完毕,同步方法1和2被打印;同时生产微任务队列(包含s1和s2)和事件队列(此时只包含f1、f2和f3);

  2. main()方法执行完后,按FIFO方式首先执行微任务队列(s1=》s2);

    ​ 当微任务(s1)内部有事件(s1->f)时,将此事件添加到事件队列末尾(此时队列包含f1、f2、f3、s1->f)。

  3. 执行完微任务队列后,按FIFO方式执行事件队列(f1=》f2=》f3=》s1->f);

    1. 当事件(f1和f3)内部含有微任务时,执行完当前事件后,立即执行此事件内部的微任务(f1->s3,f1->s4和f3->s5);
    2. 当事件内的微任务(f3->s5)内还有事件(f3->s5->f1)时,将此事件添加到事件队列末尾(此时队列包含f1、f2、f3、s1->f,f3->s5->f1)。
  4. 直至把事件队列内的事件都执行完。

4、Future的注意事项

  1. Future中的then()内注册的函数不会添加到事件队列,只是一个回调函数,它只是在事件循环中任务完成后被调用;
  2. future类完成计算后,then()注册的回调函数会立即执行;
  3. 如果futurethen()被调用之前已经完成计算,那么任务会被添加到微任务队列中,并且该任务会执行then()中注册的回调函数;
  4. Future()Future.delady()构造函数不会立即完成计算;
  5. Future.value()构造函数在微任务中完成,其他类似第3条;
  6. 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

图例:
在这里插入图片描述
解析:

  1. main()方法执行完后,会把同步方法执行完,即途中①后面的;

  2. 执行完同步方法后,按FIFO方式先执行微任务队列中的任务;

    Future.sync()方法在构造方法立即执行,将任务(sync1–then和sync2–then)放到微任务队列中;

  3. 执行完微任务队列后,按FIFO方式执行事件队列(f1、f2、f3、d1)中的事件;

    1. f1在then()方法调用前就完成计算,所以内部事件被放到微任务队列做任务(f1–1);
    2. f2内部中有一个匿名事件,此时会将此匿名事件添加到事件队列队尾(f1、f2、f3、d1、匿名f1-3);
    3. f2执行时,会执行内部的f1的微任务(f1-1),
    4. d1是延时事件,会在1分钟后执行
    5. 最后执行匿名f1-1。
  4. 事件队列执行完毕后结束。

注意点:

  1. 尽量使用事件队列,微任务要尽量简单,否则会引起鼠标无反应等。
  2. 为使应用程序保持响应,避免在事件循环中添加计算密集型代码
  3. 执行计算密集型代码的时候,另创建Isolate

六、生成器

Dart中生成器有两种类型:

  1. 同步生成器: sync* —>返回Iterable对象
  2. 异步生成器: 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,完成。
  1. 在yield语句中,为current赋的值;
  2. 调用moveNext()才会跳进getNum()内;
  3. moveNext()返回的是bool值,当有数据时返回true。
  4. 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结束。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值