美文网首页
Flutter 异步编程

Flutter 异步编程

作者: 黑色茄子 | 来源:发表于2022-10-08 18:42 被阅读0次

    Flutter 架构

    Flutter框架分三层
    Framework,Engine, Embedder


    17849932-9b65096d82b2c069.jpg

    Framework使用dart语言实现,包括UI,文本,图片,按钮等Widgets,渲染,动画,手势等。此部分的核心代码是flutter仓库下的flutter package,以及sky_engine仓库下的 io, async, ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。

    Engine使用C++实现,主要包括:Skia, Dart 和 Text。

    Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。其已作为Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS等其他众多产品的图形引擎,支持平台还包括Windows, macOS, iOS,Android,Ubuntu等。
    Dart 部分主要包括:Dart Runtime,Garbage Collection(GC),如果是Debug模式的话,还包括JIT(Just In Time)支持。Release和Profile模式下,是AOT(Ahead Of Time)编译成了原生的arm代码,并不存在JIT部分。
    Text 即文本渲染,其渲染层次如下:衍生自 Minikin的libtxt库(用于字体选择,分隔行);HartBuzz用于字形选择和成型;Skia作为渲染/GPU后端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics来渲染字体。

    Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去,Embedder的主要工作包括渲染Surface设置, 线程设置,以及插件等。平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

    Flutter的线程管理

    Flutter Engine自己不创建和管理线程。Flutter Engine线程的创建和管理是由Embedder负责的
    Embedder提供四个Task Runner, 每个Task Runner负责不同的任务,Flutter Engine不在乎Task Runner具体跑在哪个线程,但是它需要线程配置在整一个生命周期里面保持稳定。也就是说一个Runner最好始终保持在同一线程运行


    1568944133399-27d39b24-ab41-43c9-b41c-7f04748b8a14.png
    Platform Task Runner

    Flutter Engine的主Task Runner,运行Platform Task Runner的线程可以理解为是主线程。类似于Android Main Thread或者iOS的Main Thread。但是我们要注意Platform Task Runner和iOS之类的主线程还是有区别的。

    对于Flutter Engine来说Platform Runner所在的线程跟其它线程并没有实质上的区别,只不过我们人为赋予它特定的含义便于理解区分。实际上我们可以同时启动多个Engine实例,每个Engine对应一个Platform Runner,每个Runner跑在各自的线程里。这也是Fuchsia(Google正在开发的操作引擎)里Content Handler的工作原理。一般来说,一个Flutter应用启动的时候会创建一个Engine实例,Engine创建的时候会创建一个线程供Platform Runner使用。

    跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread,试图在其它线程中调用Flutter Engine会导致无法预期的异常。这跟iOS UI相关的操作都必须在主线程进行相类似。需要注意的是在Flutter Engine中有很多模块都是非线程安全的。一旦引擎正常启动运行起来,所有引擎API调用都将在Platform Thread里发生。

    Platform Runner所在的Thread不仅仅处理与Engine交互,它还处理来自平台的消息。这样的处理比较方便的,因为几乎所有引擎的调用都只有在Platform Thread进行才能是安全的,Native Plugins不必要做额外的线程操作就可以保证操作能够在Platform Thread进行。如果Plugin自己启动了额外的线程,那么它需要负责将返回结果派发回Platform Thread以便Dart能够安全地处理。规则很简单,对于Flutter Engine的接口调用都需保证在Platform Thread进行。

    需要注意的是,阻塞Platform Thread不会直接导致Flutter应用的卡顿(跟iOS android主线程不同)。尽管如此,平台对Platform Thread还是有强制执行限制。所以建议复杂计算逻辑操作不要放在Platform Thread而是放在其它线程(不包括我们现在讨论的这个四个线程)。其他线程处理完毕后将结果转发回Platform Thread。长时间卡住Platform Thread应用有可能会被系统Watchdog强行杀死。

    UI Task Runner Thread(Dart Runner)

    UI Task Runner被Flutter Engine用于执行Dart root isolate代码(isolate我们后面会讲到,姑且先简单理解为Dart VM里面的线程)。Root isolate比较特殊,它绑定了不少Flutter需要的函数方法。Root isolate运行应用的main code。引擎启动的时候为其增加了必要的绑定,使其具备调度提交渲染帧的能力。对于每一帧,引擎要做的事情有:

    • Root isolate通知Flutter Engine有帧需要渲染。

    • Flutter Engine通知平台,需要在下一个vsync的时候得到通知。

    • 平台等待下一个vsync

    • 对创建的对象和Widgets进行Layout并生成一个Layer Tree,这个Tree马上被提交给Flutter Engine。当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述。

    • 创建或者更新Tree,这个Tree包含了用于屏幕上显示Widgets的语义信息。这个东西主要用于平台相关的辅助Accessibility元素的配置和渲染。

    除了渲染相关逻辑之外Root Isolate还是处理来自Native Plugins的消息响应,Timers,Microtasks和异步IO。 我们看到Root Isolate负责创建管理的Layer Tree最终决定什么内容要绘制到屏幕上。因此这个线程的过载会直接导致卡顿掉帧。 如果确实有无法避免的繁重计算,建议将其放到独立的Isolate去执行,比如使用compute关键字或者放到非Root Isolate,这样可以避免应用UI卡顿。但是需要注意的是非Root Isolate缺少Flutter引擎需要的一些函数绑定,你无法在这个Isolate直接与Flutter Engine交互。所以只在需要大量计算的时候采用独立Isolate。

    GPU Task Runner

    GPU Task Runner被用于执行设备GPU的相关调用。UI Task Runner创建的Layer Tree信息是平台不相关,也就是说Layer Tree提供了绘制所需要的信息,具体如何实现绘制取决于具体平台和方式,可以是OpenGL,Vulkan,软件绘制或者其他Skia配置的绘图实现。GPU Task Runner中的模块负责将Layer Tree提供的信息转化为实际的GPU指令。GPU Task Runner同时也负责配置管理每一帧绘制所需要的GPU资源,这包括平台Framebuffer的创建,Surface生命周期管理,保证Texture和Buffers在绘制的时候是可用的。

    基于Layer Tree的处理时长和GPU帧显示到屏幕的耗时,GPU Task Runner可能会延迟下一帧在UI Task Runner的调度。一般来说UI Runner和GPU Runner跑在不同的线程。存在这种可能,UI Runner在已经准备好了下一帧的情况下,GPU Runner却还正在向GPU提交上一帧。这种延迟调度机制确保不让UI Runner分配过多的任务给GPU Runner。

    前面我们提到GPU Runner可以导致UI Runner的帧调度的延迟,GPU Runner的过载会导致Flutter应用的卡顿。一般来说用户没有机会向GPU Runner直接提交任务,因为平台和Dart代码都无法跑进GPU Runner。但是Embeder还是可以向GPU Runner提交任务的。因此建议为每一个Engine实例都新建一个专用的GPU Runner线程。

    IO Task Runner

    前面讨论的几个Runner对于执行任务的类型都有比较强的限制。Platform Runner过载可能导致系统WatchDog强杀,UI和GPU Runner过载则可能导致Flutter应用的卡顿。但是GPU线程有一些必要操作是比较耗时间的,比如IO,而这些操作正是IO Runner需要处理的。

    IO Runner的主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备。在Texture的准备过程中,IO Runner首先要读取压缩的图片二进制数据(比如PNG,JPEG),将其解压转换成GPU能够处理的格式然后将数据上传到GPU。这些复杂操作如果跑在GPU线程的话会导致Flutter应用UI卡顿。但是只有GPU Runner能够访问GPU,所以IO Runner模块在引擎启动的时候配置了一个特殊的Context,这个Context跟GPU Runner使用的Context在同一个ShareGroup。事实上图片数据的读取和解压是可以放到一个线程池里面去做的,但是这个Context的访问只能在特定线程才能保证安全。这也是为什么需要有一个专门的Runner来处理IO任务的原因。获取诸如ui.Image这样的资源只有通过async call,当这个调用发生的时候Flutter Framework告诉IO Runner进行刚刚提到的那些图片异步操作。这样GPU Runner可以使用IO Runner准备好的图片数据而不用进行额外的操作。

    用户操作,无论是Dart Code还是Native Plugins都是没有办法直接访问IO Runner。尽管Embeder可以将一些一般复杂任务调度到IO Runner,这不会直接导致Flutter应用卡顿,但是可能会导致图片和其它一些资源加载的延迟间接影响性能。所以建议为IO Runner创建一个专用的线程。

    Event Loop

    和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程称为isolate。应用程序启动后,开始执行main函数并运行main isolate。

    每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列,event和microtask队列有点类似iOS的source0和source1。iOS开发中的RunLoop

    • event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。
    • microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。


      2e42a13b1eeb2ed673350e7adca16c40.png

    Dart事件循环执行如图所示

    先查看microtask queue是否为空,不是则先执行microtask queue
    一个microtask执行完后,检查有没有下一个microtask,直到microtask queue为空,才去执行event queue
    在event queue取出一个事件处理完后,再次返回第一步,去检查microtask queue是否为空
    注意:我们可以看出,将任务加入到microtask queue中可以被尽快执行,但也需要注意,当事件循环在处理microtask queue时,event queue会被卡住,应用程序无法处理手势+单击、I/O消息等等事件。

    Isolate

    可以把它理解为Dart中的线程。但它又不同于线程,它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。


    1568944133391-04a7f7f6-cd89-4953-b1f1-30da235aadb7.png

    Isolate 类是用来管理线程 包括创建spawn / spawnUri , 暂停 pause, 杀死 kill

    Future<Isolate> Isolate.spawn(entryPoint, message)
    
    • entryPoint(必须是一个顶层方法或静态方法)
    • message
      •① Dart 原始数据类型,如null. bool int. double、string 等
      •② Sendport 实例 - ReceivePort().sendPort
      •包含①和 ②的list 和 map,也可以嵌套
    void multiThread() {
      print('multi thread start');
    
      print('当前线程: ${Isolate.current.debugName}');
      Isolate.spawn(newThread1, 'hello1');
      Isolate.spawn(newThread2, 'hello2');
      Isolate.spawn(newThread3, 'hello3');
    
      print('multi thread end');
    }
    
    void newThread1(String message) {
      print('当前线程: ${Isolate.current.debugName}');
      print(message);
    }
    
    void newThread2(String message) {
      print('当前线程: ${Isolate.current.debugName}');
      print(message);
    }
    
    void newThread3(String message) {
      print('当前线程: ${Isolate.current.debugName}');
      print(message);
    }
    

    多次运行的话 会发现因为是异步执行 这些打印输出的顺序是不固定的

    multi thread start
    当前线程: main
    当前线程: newThread1
    hello1
    multi thread end
    当前线程: newThread2
    hello2
    当前线程: newThread3
    hello3
    

    lsolate 多线程之间,通信的唯一方式是 Port

    • ReceivePort 类: 初始化接收端口,创建发送端口,接受消息,监听消息,关闭端口
    • SendPort 类: 将消息发送给 ReceivePort
      通信方式
      •单向通信(A->B)
      •双向通信 (A<>B)


      Isolate单向通信.png
      Isolate双向通信.png
    单向通信
    void multiThread() {
      print('multiThread start');
      print('当前线程: ${Isolate.current.debugName}');
      ReceivePort r1 = ReceivePort();
      SendPort p1 = r1.sendPort;
      Isolate.spawn(newThread1, p1);
    
      // 接收新线程发送过来的消息
      // var msg = r1.first;
      // print('来自新线程的消息: $msg');
    
      // r1.first.then((value) => print('来自新线程的消息: $value'));
    
      // r1.listen((value) {
      //   print('来自新线程的消息: $value');
      //   // 调用close可以把当前监听关闭,如果不关闭则会一直监听
      //   r1.close();
      // });
    
      r1.forEach((element) {
        print('来自新线程的消息: $element');
      });
    
      print('multiThread end');
    }
    
    void newThread1(SendPort p1) {
      print('当前线程: ${Isolate.current.debugName}');
      // 发送消息给main线程
      p1.send('abc');
    }
    
    multiThread start
    当前线程: main
    当前线程: newThread1
    multiThread end
    来自新线程的消息: abc
    
    双向通信
    void multiThread() async {
      print('multiThread start');
      print('当前线程: ${Isolate.current.debugName}');
      ReceivePort r1 = ReceivePort();
      SendPort p1 = r1.sendPort;
      Isolate.spawn(newThread, p1);
    
      SendPort p2 = await r1.first;
      // p2.send('来自主线程的消息');
    
      var msg = await sendToReceive(p2, 'hello');
      print('主线程接收到的消息:$msg');
    
      var msg2 = await sendToReceive(p2, 'dart');
      print('主线程接收到的消息:$msg2');
    
      var msg3 = await sendToReceive(p2, 'from main');
      print('主线程接收到的消息:$msg3');
    
      print('multiThread end');
    }
    
    void newThread(SendPort p1) async {
      print('当前线程: ${Isolate.current.debugName}');
      // 发送消息给main线程
      // p1.send('abc');
    
      ReceivePort r2 = ReceivePort();
      SendPort p2 = r2.sendPort;
      p1.send(p2);
    
      // r2.listen((message) {
      //   print(message);
      // });
    
      await for (var item in r2) {
        var data = item[0];
        print('新线程接收到了来自主线程的消息:$data');
        SendPort replyPort = item[1];
        // 给主线程回复消息
        replyPort.send('I get $data');
      }
    }
    
    Future sendToReceive(SendPort port, msg) {
      print('发送消息给新线程:$msg');
      ReceivePort response = ReceivePort();
      port.send([msg, response.sendPort]);
      return response.first;
    }
    
    multiThread start
    当前线程: main
    当前线程: newThread
    发送消息给新线程:hello
    新线程接收到了来自主线程的消息:hello
    主线程接收到的消息:I get hello
    发送消息给新线程:dart
    新线程接收到了来自主线程的消息:dart
    主线程接收到的消息:I get dart
    发送消息给新线程:from main
    新线程接收到了来自主线程的消息:from main
    主线程接收到的消息:I get from main
    multiThread end
    
    SpawnUri
    Future<Isolate>spawnUri(uri, args, message)
    

    spawnUri方法有三个必须的参数,

    • 第一个是Uri,指定一个新Isolate代码文件的路径,
    • 第二个是参数列表
    • message
      需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,该main函数中的args参数列表,正对应spawnUri中的第二个参数。如不需要向新Isolate中传参数,该参数可传空List

    主Isolate中的代码:

    void main(List<String> args) {
      start();
    
      newIsolate();
    
      init();
    }
    
    newIsolate() async {
      print("新线程创建");
    
      ReceivePort r = ReceivePort();
    
      SendPort p = r.sendPort;
    
      //执行文件所在路径
      Isolate childIsolate = await Isolate.spawnUri(
          Uri(path: "./lib/child_isolate.dart"), ["date1", "date1"], p);
    
      Isolate.spawnUri(uri, args, message)
      
      r.listen((message) {
        print("主线程接收到的数据 ${message[0]}");
    
        if (message[1] == 2) {
          r.close(); // 数据接收完成 关闭通道
    
          childIsolate.kill(); // 杀死新线程,释放资源
        }
      });
    }
    
    start() {
      print("应用启动 ${DateTime.now().microsecondsSinceEpoch.toString()}");
    }
    
    init() {
      print("项目初始化");
    }
    

    子Isolate中的代码:

    void main(List<String> args, SendPort mainSendPort) {
      print("新线程接收到的参数 ${args}");
    
      mainSendPort.send(['开始执行异步操作', 0,]);
    
      sleep(Duration(seconds: 1));
    
      mainSendPort.send(['加载中', 1,]);
    
      sleep(Duration(seconds: 1));
    
      mainSendPort.send(['异步完成', 2,]);
    }
    

    运行输出

    multiThread start
    当前线程: main
    当前子线程: newThread
    发送消息给新线程:hello
    新线程接收到了来自主线程的消息:hello
    主线程接收到的消息:I get hello
    发送消息给新线程:dart
    新线程接收到了来自主线程的消息:dart
    主线程接收到的消息:I get dart
    发送消息给新线程:from main
    新线程接收到了来自主线程的消息:from main
    主线程接收到的消息:I get from main
    multiThread end
    
    Flutter Engine Runners与Dart Isolate

    既然Flutter Engine有自己的Runner,那为何还要Dart的Isolate呢
    Dart的Isolate是Dart虚拟机自己管理的,Flutter Engine无法直接访问。Root Isolate通过Dart的C++调用能力把UI渲染相关的任务提交到UI Runner执行这样就可以跟Flutter Engine相关模块进行交互,Flutter UI相关的任务也被提交到UI Runner也可以相应的给Isolate一些事件通知,UI Runner同时也处理来自App方面Native Plugin的任务。
    所以简单来说Dart isolate跟Flutter Runner是相互独立的,他们通过任务调度机制相互协作。

    Isolate的消耗

    在时间上, 通常来说,当我们使用多线程计算的时候,整个计算的时间会比单线程要多,额外的耗时是

    • 创建 Isolate
    • Copy Message
    • 销毁 Isolate

    我们是在 Main 线程之外的 isolate 请求的数据,在该线程进行解析,最后传回 Main Isolate,经历了一次 isolate 的创建以及销毁过程。这将会耗费约 50-150ms 的时间

    在空间上, Isolate 实际上是比较重的,每当我们创建出来一个新的 Isolate 至少需要 2mb 左右的空间甚至更多.
    dart team 已经为我们写好一些非常实用的 package,其中就包括 LoadBalancer

    LoadBalancer

    在 pubspec.yaml 中添加 isolate 的依赖。

    isolate: ^2.0.2
    

    具体使用如下

    import 'package:isolate/isolate.dart';
    
    Future<int> useLoadBalancer() async {
    
      //可以通过 LoadBalancer 创建出指定个数的 isolate
      Future<LoadBalancer> loadBalancer = LoadBalancer.create(1, IsolateRunner.spawn);
      final lb = await loadBalancer;
      int res = await lb.run<int, int>(_doSomething1, 1);
      return res;
    }
    
    Future<int> _doSomething(int v){
      return Future.delayed(Duration(seconds: 2), () {
        print("doSmoething");
        return 22;
      });
    }
    
    int _doSomething1(int v){
      print("doSmoething1 $v");
      return 2222;
    }
    
    void main(List<String> args) async {
      final v = await useLoadBalancer();
      print(v);
    }
    
    控制台输出为
    doSmoething1 1
    2222
    

    我们关注的只有 Future<R> run<R, P>(FutureOr<R> function(P argument), argument, 方法。我们还是需要传入一个 function 在某个 isolate 中运行,并传入其参数 argument。run 方法将会返回我们执行方法的返回值。
    需要注意的是,使用时只能传递一个参数,返回值也只能有一个
    我们可以通过 LoadBalancer 创建出指定个数的 isolate
    LoadBalancer将会创建出一个 isolate 线程池,并自动实现了负载均衡
    当我们多次使用额外的 isolate 的时候,不再需要重复创建了。
    并且 LoadBalancer 还支持 runMultiple,可以让一个方法在多线程中执行

    Compute

    由于dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作
    要使用compute,必须注意的有两点
    第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,
    第二个参数为动态的消息类型,可以是被运行函数的参数。
    compute传参,只能传递一个参数,返回值也只有一个, 这一点与LoadBalancer一样

    import 'package:flutter/foundation.dart';
    
    int func2(int v) {
      print("子进程收到参数:$v");
      print("子进程执行耗时操作");
      var sum = 0;
      for (int i = 0; i < 9999999; i++) {
        sum = i;
      }
      return sum;
    }
    
    int func1(int v1, int v2){
      print("子进程收到参数:$v1 $v2");
      print("子进程执行耗时操作");
      var sum = 0;
      for (int i = 0; i < 9999999; i++) {
        sum = i;
      }
      return sum;
    }
    
    List<String> func3(List<String> v){
      print("子进程收到参数:$v");
      print("子进程执行耗时操作");
      var sum = 0;
      for (int i = 0; i < 9999999; i++) {
        sum = i;
      }
      return ['$sum'];
    }
    
    void computeTest() async {
      print('外部代码1');
      // 这里需要使用外部方法,不能使用匿名函数,不能使用闭包
      // 只能传递一个参数,返回值也只有一个
      // var result = await compute(func1, 10);  //超过一个传参会报错
      // var result = await compute(func3, ['10']);
      var result = await compute(func2, 10);
      print("执行完成:$result");
      print('外部代码2');
    }
    
    void main(List<String> args) async {
      computeTest();
    }
    
    控制台输出为
    I/flutter ( 5243): 外部代码1
    I/flutter ( 5243): 子进程收到参数:10
    I/flutter ( 5243): 子进程执行耗时操作
    I/flutter ( 5243): 执行完成:9999998
    I/flutter ( 5243): 外部代码2
    

    compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的Isolate

    Future

    Future 是 Dart 中的类,我们可以通过 Future 实例,封装一些异步任务
    Future 的含义是未来。未来要执行的一些任务,我们可以放到Future中
    Future 有三种状态
    •未完成 (Uncompleted)
    •已完成,并返回数据 (Completed with data)
    •已完成,但返回报错 (Completed with error)

    Future 状态相关的返回

    • 创建
      • Uncompleted
    • then()
      • Completed with data
    • catchError()
      • Completed with error
    • whenComplete()
      • Completed with data + Completed with error

    Future 的执行顺序
    Future 默认是异步任务,会被丢到事件队列 (event queue)中

    • Future.sync()
      •同步任务,同步执行(不会被丢到异步队列中)
    • Future.microtask()
      •微任务,会丟到microtask queue 中,优先级比事件任务高
    • Future.value(val)
      •val是常量(等同于 microtask)
      •val 是异步(按照异步逻辑处理)

    获取 Future 实例

    //自动返回
    final myFuture = http.get('https://my.image.url');
    final myFuture = SharedPreferences.getInstance;
    //手动创建
    final myFuture = Future(() { return 123; })
    final myFuture = Future.error(Exception());
    
    delayed
      // 指定延迟时间
      Future.delayed(Duration(seconds: 2), () {
        // 回调函数
        // throw Error();
        return 123;
      }).then((value) => print(value)).catchError((error) {
        print("报错:$error");
      }, test:(error) => error.runtimeType == String,
      ).whenComplete(() {
        print("完成了 无论失败成功");
      });
    
    microtask
      print("start");
    
      Future((() => print("Future task"))); // 宏任务
    
      Future.value("Future.value").then((value) => print(value)); //微任务
    
      Future.microtask(() => print("Future.microtask")); //微任务
    
      Future.sync(() => print("Future.sync")); // 同步任务
    
      Future.value(Future((() => print("Future.value.Future")))); //宏任务
    
      print("end");
    
    控制台输出为
    start
    Future.sync
    end
    Future.value
    Future.microtask
    Future task
    Future.value.Future
    
    any 和 wait
      final f1 = Future.delayed(Duration(seconds: 4), (() => 1));
      final f2 = Future.delayed(Duration(seconds: 3), (() => 2));
      final f3 = Future.delayed(Duration(seconds: 2), (() => 3));
      final f4 = Future.delayed(Duration(seconds: 5), (() => 4));
    
      //返回最先完成的future结果
      // Future.any([f1, f2, f3, f4]).then((value) {
      //   print(value);
      // });
    //等待所有future执行完成, 并收集所有future的返回结果
    Future.wait([f1, f2, f3, f4]).then((value) {
        print(value);
      });
    
    FutureBuilder

    FutureBuilder 是 Flutter SDK 中提供的异步组件。
    FutureBuilder 是一个类,接受 Future数据,并将数据渲染成界面
    FutureBuilder 中,有三个属性
    • future
    • initialData
    • builder(context, snapshot)

    • snapshot.connectionState
      • ConnectionState.none(未连接异步任务)
      • ConnectionState waitine(连接异步任务,等待交互)
      • ConnectionState.active(正在交互)
      • Connectionstate.done(异步任务完成)
    • snapshot.hasData (Completed with data)
      • snapshot.data
    • snapshot.hasError (Completed with error)
      FutureBuilder(
          future: _getData(), // 异步任务
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.active:
              case ConnectionState.waiting:
                print("waiting");
                //未完成状态 数据处理
                return null;
              case ConnectionState.done:
                if (snapshot.hasError) {
                  print("error");
                //报错 数据处理
                  return null;
                } else {
                  print("done");
                  //请求完成 数据处理
                  return snapshot.data;
                }
            }
          });
    
    Isolate与 Future 如何选择

    •两者都可以执行异步操作,但逻辑不同
    •lsolate 的开销比 Future 要大
    •lsolate 需要重新开启线程,Future 是单线程内的异步任务
    •异步任务耗时短,推荐使用Future,耗时长,推荐使用 lsolate
    •如果使用 Future 来处理耗时长的异步任务,会造成阻塞
    •耗时<100ms选Future;耗时>100 毫秒 选 lsolate

    Stream

    Stream 是 Dart 中的异步数据流,可以连续不断的返回多个数据。
    Stream 相关的 API
    • listen 进行数据监听
    • error 接收失败状态
    • done 接收结束状态

    Stream的类型

    • Single-Subscription(单一订阅)
      • 数据流只能被 listen 一次(listen 多次会报错)
      • StreamController().stream
      • Stream stream = Stream.fromIterable(data)
    • Broadcast(广播)
      • 数据流可以被 listen 多次
      • StreamController<int>.broadcast();
      • Stream.asBroadcastStream()
    Single-Subscription
      // 创建一个数据流
      final StreamController controller = StreamController();
    
      // 第一次监听
      controller.stream.listen((event) {
        print("$event");
      });
    
      // 第二次监听  会报错
      // controller.stream.listen((event) {
      //   print("$event");
      // });
    
      controller.sink.add("单一数据流数据");
      controller.sink.add("单一数据流数据22");
      controller.sink.add("单一数据流数据2233");
      controller.sink.add("单一数据流数据2241234");
      controller.sink.add("单一数据流数据231231232");
    
    控制台输出为
    单一数据流数据
    单一数据流数据22
    单一数据流数据2233
    单一数据流数据2241234
    单一数据流数据231231232
    
    Broadcast
      StreamController controller = StreamController.broadcast();
    
      // 第一次监听
      controller.stream.listen((event) {
        print("11 $event");
      });
    
      // 第二次监听
      controller.stream.listen((event) {
        print("22 $event");
      });
    
      controller.sink.add("单一数据流数据");
      controller.sink.add("单一数据流数据22");
      controller.sink.add("单一数据流数据2233");
      controller.sink.add("单一数据流数据2241234");
      controller.sink.add("单一数据流数据231231232");
    
    控制台输出为
    11 单一数据流数据
    22 单一数据流数据
    11 单一数据流数据22
    22 单一数据流数据22
    11 单一数据流数据2233
    22 单一数据流数据2233
    11 单一数据流数据2241234
    22 单一数据流数据2241234
    11 单一数据流数据231231232
    22 单一数据流数据231231232
    

    Stream的相关操作
    • Stream.fromFuture() 创建一个Single-Subscription, 当future返回结果时 做出处理
    • Stream.fromFutures() 创建一个Single-Subscription, 当一组future返回结果时 做出处理
    • Stream.fromIterable() 从集合中创建一个新的Single-Subscription
    • Stream.periodic() 创建流,在周期间隔反复广播事件

    具体示例如下

    fromFuture
    Future<String> getData(int seconds) {
      return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
    }
    
    void main(List<String> args) {
      Stream.fromFuture(getData(2)).listen((event) {
        print("fromFuture $event");
      }).onDone(() {
        print("fromFuture done");
      });
    }
    
    控制台输出为
    fromFuture 2022-10-08 14:13:52.077248
    fromFuture done
    
    fromFutures
    Future<String> getData(int seconds) {
      return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
    }
    
    void main(List<String> args) {
      Stream.fromFutures([getData(1), getData(3)]).listen((event) {
        print("fromFutures $event");
      }).onDone(() {
        print("fromFutures done");
      });
    }
    
    控制台输出为
    fromFutures 2022-10-08 14:15:15.094200
    fromFutures 2022-10-08 14:15:17.090584
    fromFutures done
    
    fromIterable
    Future<String> getData(int seconds) {
      return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
    }
    
    void main(List<String> args) {
      Stream.fromIterable([getData(1), getData(3)]).listen((event) {
        print("fromFutures $event");
      }).onDone(() {
        print("fromFutures done");
      });
    }
    
    控制台输出为
    fromFutures Instance of 'Future<String>'
    fromFutures Instance of 'Future<String>'
    fromFutures done
    
    periodic
    Future<String> getData(int seconds) {
      return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
    }
    
    void main(List<String> args) {
      Duration interval = Duration(seconds: 2);
      // 不带第二个参数的话 会返回null
      // Stream<int> stream = Stream<int>.periodic(interval);
    
      // stream.listen((event) {
      //   print("periodic $event");
      // }).onDone(() {
      //   print("done");
      // });
    
      // 带有第二个参数 返回具体的数据
    
      Stream<int> stream1 = Stream<int>.periodic(interval, (data) => data);
    
      stream1.listen((event) {
        print("periodic $event");
      }).onDone(() {
        print("done");
      });
    }
    
    控制台输出为 
    periodic 0
    periodic 1
    periodic 2
    periodic 3
    periodic 4
    periodic 5
    .....
    
    StreamBuilder

    StreamBuilder 是 Flutter SDK 中提供的异步组件。
    StreamBuilder是一个类,接受 stream 数据,并将数据渲染成界面
    StreamBuilder中,有三个属性
    • stream
    • initialData
    • builder(context, snapshot)
    snapshot与FutureBuilder相同
    StreamBuilder的用法也与FutureBuilder相类似 这里就不赘述

    async、await

    async和await的本质是协程(coroutine)的语法糖,协程可以让单线程支持异步调度的,减少进程调度带来的开销

    • async:标记函数是一个异步函数,其返回值类型是 Future
    • await:等待某个异步方法执行完毕
      •用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码
    • 作用
      •await 会等待异步任务执行(相当于将异步转成同步)
      •async-await 简化代码,防止回调地狱的产生
    getUserInfo() {
      return Future.delayed(Duration(seconds: 2), () {
        print("UserInfo");
        return 1;
      });
    }
    
    getOrderInfo(int id){
      return Future.delayed(Duration(seconds: 2), () {
        print("当前用户: $id, OrderInfo");
        return 22;
      });
    }
    
    void main(List<String> args) async {
      print("start");
      //避免回调地狱
      var id = await getUserInfo();
      var order = await getOrderInfo(id);
      print(order);
    
      print("end");
    }
    
    控制台输出为 
    start
    UserInfo
    当前用户: 1, OrderInfo
    22
    end
    

    参考资料

    Dart 中的并发
    Flutter Engine线程管理与Dart Isolate机制
    Dart异步编程
    dart学习-- Dart之异步编程
    Flutter异步编程

    相关文章

      网友评论

          本文标题:Flutter 异步编程

          本文链接:https://www.haomeiwen.com/subject/fehpartx.html