Flutter异步编程

2022-08-27  本文已影响0人  yuLiangC

单线程异步

一般来说,dart是单线程的,通常我们的flutter代码都是运行在一个线程里,并无主次线程之分,除非自己新开了一个isolate,否则线程不会切换。但是同一个线程里dart却能实现异步编程,那么它的异步是怎么实现的呢?

事件循环

dart的主线程执行的是同步任务,但它内部维护了一个事件循环(Event Loop)和两个任务队列(Event queue和Microtask queue),它们负责执行异步任务。
Event queue:io、timer、绘制事件等。
Microtask queue:加入微任务中的事件。优先级最高

执行流程

image.png

优先级顺序依次为:同步任务 > Microtask queue > Event queue,即每次同步任务执行完毕后,eventLooper会先轮询检查微任务队列,按照先进先出的顺序一次执行微任务队列,当微任务队列当中的任务执行完毕之后再轮询检查事件队列,依然按照先进先出的顺序依次执行事件队列当中的任务。
例如:

main(){
  print('main1');
  Timer.run(() { print('timer1'); });
  Timer.run(() { print('timer2'); });
  print('main2');
}

执行结果为:

main1
main2
timer1
timer2

虚拟机调用机制

和Java类似,dart虚拟机在运行dart程序时,会涉及到以下几种数据结构:
1.stack 方法调用栈,用于方法调用
2.heap 堆,用于存储对象,可主动销毁
3.Queue 任务队列,用于存储异步任务
4.Event Dispatcher 它会将队列中的任务依次取出然后同步执行。
示例2:

  print('main1');
  Timer.run(() {
    scheduleMicrotask(() {
      print('microtask1');
    });
    scheduleMicrotask(() {
      print('microtask2');
    });
  });
  scheduleMicrotask(() {
    print('microtask3');
    Timer.run(() {
      print('timer 1');
    });
  });
  Timer.run(() {
    print('timer2');
  });
  print('main2');

该函数依次执行的顺序如下:

main1
main2
microtask1
microtask2
microtask3
timer2
timer1

为何会是这种结果呢?从数据流转的顺序看,我们一步步的分析,打印main1 -> microtask1()方法加入微任务队列 -> microtask2加入微任务队列 -> microtask3加入微任务队列 -> timer2()加入事件队列 ->打印main2,至此,同步任务都执行完了,接下来执行异步任务,异步任务是先轮训微任务队列,因此顺序是:打印microtask1 -> 打印microtask2 -> 打印microtask3-> time1()加入事件队列;再执行事件队列任务:打印timer2 -> 打印timer1。

为何单线程可以异步

因为主线程多数时候都是处于等待状态,是比较空闲的,等待用户交互、网络请求结果或者io操作结果。
而这个等待的过程并不是阻塞的,一个线程里任何任务都可以拆分成最基本的操作命令,这些命令有些是计算或者存储,有些是缓存和总线等内存读取工作,它们分属不同的元器件来执行,CPU的执行效率通常比较高,而io读写等操作比较耗时,因此当CPU执行当前任务到需要等待数据输入时会把当前任务挂起,继续执行下一条任务,而当数据读取完成后又继续执行挂起的任务。这样就可以大大提高CPU的利用效率。
这样设计的好处就是在提高资源利用效率的同时,避免了多线程的死锁以及资源频繁切换问题。

Future

dart的异步是通过Future函数来实现的,Future顾名思义,就是未来、期货的意思,不会马上执行,代表异步任务。其内部实现实际就是一个Timer,将事件推入事件队列当中去处理。

Future函数通常返回的也是Future,因此它后面可以链式的调用无数个then。

void test3() {
  Future(() {
    print('f1');
  });

  Future(() {
    print('f2');
  }).then((value) {
    print('f3');
    scheduleMicrotask(() {
      print('f4');
    });
  }).then((value) => print('f5'));

  Future(() {
    print('f6');
  }).then((value) {
    Future(() {
      print('f7');
    }).then((value) => print('f8'));
  });
  Future(() {
    print('f9');
  });
  scheduleMicrotask(() {
    print('f10');
  });
  print('f11');
}

这个实例分析就不一一解释了,最后的输出结果是:

f11
f10
f1
f2
f3
f5
f4
f6
f9
f7
f8

多线程

Isolate

有了单线程异步,是不是就已经足够了呢?并没有,当遇到一些CPU密集型的任务时,单线程并不能最大效率的利用计算机资源,多核资源会闲置。因此如果需要执行一些并发任务就需要充分利用计算机的多核资源,为此dart设计了isloate多线程模式。
dart多线程是由isolate来实现的。
isolate,字面意思是隔离,因此可以看出它和我们一般意义上的线程Thread是不一样的。

单向通信

Isloate的创建通常使用Isolate.spawn方法,它可以接受两个参数,第一个是新Isolate内部执行的方法,第二个是传入新Isloate的参数。
dart提供了一个ReceivePort用来进行Isolate通信,ReceivePort提供了一个sendPort,我们可以将这个sendPort传入新isolate,这样就可以在第一个方法里面利用sendPort发送消息。
最后利用ReceivePort的listen方法监听新Isolate传过来的消息。
实例如下:

void test6() async {
  print('current ='+Isolate.current.debugName.toString());
  ReceivePort receivePort = ReceivePort();
  Isolate isolate = await Isolate.spawn(newThread, receivePort.sendPort);
  receivePort.listen((message) {
    print('收到新isolate消息:'+message);
    receivePort.close();
    isolate.kill();
  });
}

执行结果

current =main
收到新isolate消息:send Msg from newIsolate =newThread

这个是单向通信的实例,具体流转如下图:


image.png

双向通信

同样的,Isolate也支持双向通信,道理和单向通信是相通的。都是在自己的Isolate里创建一个ReceivePort,将它的sendPort传入另外一个Isolate。比如两个Isolate,Isolate1将自己的sendPort1传入Isolate2,isolate2就可以通过sendPort1向isolate1发送消息;而isolate1怎么向isolate2发消息呢?在isolate2里面也可以创建一个ReceivePort2,通过sendPort1将它的sendPort2发送给isolate1,这样isolate1就拥有了sendPort2,就可以向sendPort2发送消息了。
流转图如下:


image.png
上一篇 下一篇

猜你喜欢

热点阅读