Flutter 异步编程

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异步编程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容