Dart异步是怎么实现

前言

Dart 是单线程的,但通过事件循环可以实现异步。

而 Future 是异步任务的封装,借助于 await 与 async,我们可以通过事件循环实现非阻塞的同步等待;

Isolate 是 Dart 中的多线程,可以实现并发,有自己的事件循环与 Queue,独占资源。Isolate 之间可以通过消息机制进行单向通信,这些传递的消息通过对方的事件循环驱动对方进行异步处理。

事件循环

单线程模型中主要就是在维护着一个事件循环(Event Loop)。

在 Dart 中,实际上有两个队列,一个事件队列(Event Queue),另一个则是微任务队列(Microtask Queue)。在每一次事件循环中,Dart 总是先去第一个微任务队列中查询是否有可执行的任务,如果没有,才会处理后续的事件队列的流程。所以,Event Loop 完整版的流程图,应该如下所示:

在Dart的单线程中,代码到底是怎样执行的呢?

  • 1、Dart的入口是main函数,所以main函数中的代码会优先执行;
  • 2、main函数执行完后,会启动一个事件循环(Event Loop)就会启动,启动后开始执行队列中的任务;
  • 3、首先,会按照先进先出的顺序,执行 微任务队列(Microtask Queue)中的所有任务;
  • 4、其次,会按照先进先出的顺序,执行 事件队列(Event Queue)中的所有任务;

那么在Flutter开发中,哪些是放在事件队列,哪些是放在微任务队列呢?

  • 所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等;
  • 而微任务通常来源于Dart内部,并且微任务非常少。这是因为如果微任务非常多,就会造成事件队列排不上队,会阻塞任务队列的执行(比如用户点击没有反应的情况);
-    事件队列包含外部事件,例如I/O, Timer,绘制事件,Future(部分情况)等等。
  
-  微任务队列则包含有Dart内部的微任务,主要是通过scheduleMicrotask,Future(部分情况)来调度。
复制代码

Future的代码是加入到事件队列还是微任务队列呢?

Future中通常有两个函数执行体:

  • Future构造函数传入的函数体
  • then的函数体(catchError等同看待)

那么它们是加入到什么队列中的呢?

  • Future构造函数传入的函数体放在事件队列中
  • then的函数体要分成三种情况:
  • 情况一:Future没有执行完成(有任务需要执行),那么then会直接被添加到Future的函数执行体后;
  • 情况二:如果Future执行完后就then,该then的函数体被放到如微任务队列,当前Future执行完后执行微任务队列;
  • 情况三:如果Future是链式调用,意味着then未执行完,下一个then不会执行;

Future

Future 默认情况下其实就是往「事件队列」里插入一个事件,当有空余时间的时候就去执行,当执行完毕后会回调 Future.then(v) 方法。

而我们也可以通过使用 Future.microtask 方法来向 「微任务队列」中插入一个任务,这样就会提高他执行的效率。

Future就是用来处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败;

需要注意的是,Future的所有API的返回值仍然是一个Future对象,所以我们可以很方便的进行链式调用;

1. await/async

  • 它们是Dart中的关键字
  • 它们可以让我们用同步的代码格式,去实现异步的调用过程。
  • 并且,通常一个async的函数会返回一个Future
Future<String> getNetworkData() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return "请求到的数据:" + result;
}

2. Future.then

在then中接收异步结果

main(List<String> args) {
  print("main function start");
  // 使用变量接收getNetworkData返回的future
  var future = getNetworkData();
  // 当future实例有返回结果时,会自动回调then中传入的函数
  // 该函数会被放入到事件循环中,被执行
  future.then((value) {
    print(value);
  }).catchError((error) { 
  // 捕获出现异常时的情况 
  print(error); 
  });
  print(future);
  print("main function end");
}

3. Future.catchError

如果异步任务发生错误,我们可以在catchError中捕获错误

4. Future.whenComplete

无论异步任务执行成功或失败都需要做一些事的场景,

比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,

第一种是分别在then或catch中关闭一下对话框,

第二种就是使用Future的whenComplete回调

未完成状态(uncompleted) 完成状态(completed)

完成状态(completed)

5. Future的链式调用

  • 我们可以在then中继续返回值,会在下一个链式的then调用回调函数中拿到返回的结果
import "dart:io";

main(List<String> args) {
  print("main function start");

  getNetworkData().then((value1) {
    print(value1);
    return "content data2";
  }).then((value2) {
    print(value2);
    return "message data3";
  }).then((value3) {
    print(value3);
  });

  print("main function end");
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    // 不再返回结果,而是出现异常
     return "network data1";
  });
}

6. Future.delayed(时间, 回调函数)

  • 在延迟一定时间时执行回调函数,执行完回调函数后会执行then的回调;
main(List<String> args) {
  print("main function start");

  Future.delayed(Duration(seconds: 3), () {
    return "3秒后的信息";
  }).then((value) {
    print(value);
  });

  print("main function end");
}



复制代码

Future与Stream区别

1、Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。

2、Future 只能表示一次异步获得的数据。

3、Stream 表示多次异步获得的数据。比如界面上的按钮可能会被用户点击多次,按钮上的点击事件(onClick)就是一个 Stream 。

4、Future将返回一个值,而Stream将返回多次值。

5、Dart 中统一使用 Stream 处理异步事件流。

Isolate

Isolate 是 Dart 中的线程

  • 比如Flutter中就有一个Root Isolate,负责运行Flutter的代码,比如UI渲染、用户交互等等;

  • Dart 中的线程是以隔离 (Isolate)的方式存在的,每个 Isolate 都有自己的 Event Loop 与 Queue

  • Isolate 之间不共享任何资源,只能依靠消息机制通信,不需要竞争资源,就不需要锁(不用担心死锁问题),因此也就没有资源抢占问题。

  • 每个Isolate 都有自己独立的,私有内存块(多个线程不共享内存)

但是,如果只有一个Isolate,那么意味着我们只能永远利用一个线程,这对于多核CPU来说,是一种资源的浪费。

如果在开发中,我们有非常多耗时的计算,完全可以自己创建Isolate,在独立的Isolate中完成想要的计算操作。

什么时候使用Isolate

1、如果一段代码不会被中断,那么就直接使用正常的同步执行就行。

2、如果代码段可以独立运行而不会影响应用程序的流畅性,建议使用 Future (需要花费几毫秒时间)

3、如果繁重的处理可能要花一些时间才能完成,而且会影响应用程序的流畅性,建议使用 isolate (需要几百毫秒)

下面列出一些使用 isolate 的具体场景:

1、JSON解析: 解码JSON,这是HttpRequest的结果,可能需要一些时间,可以使用封装好的 isolate 的 compute 顶层方法。

2、加解密: 加解密过程比较耗时

3、图片处理: 比如裁剪图片比较耗时

4、从网络中加载大图

如何创建Isolate呢?

Isolate通信机制

但是在真实开发中,我们不会只是简单的开启一个新的Isolate,而不关心它的运行结果:

  • 我们需要新的Isolate进行计算,并且将计算结果告知Main Isolate(也就是默认开启的Isolate);
  • Isolate 通过发送管道(SendPort)实现消息通信机制;
  • 我们可以在启动并发Isolate时将Main Isolate的发送管道作为参数传递给它;
  • 并发在执行完毕时,可以利用这个管道给Main Isolate发送消息;
import "dart:isolate";

main(List<String> args) async {
  // 1.创建管道
  ReceivePort receivePort= ReceivePort();

  // 2.创建新的Isolate
  Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);

  // 3.监听管道消息
  receivePort.listen((data) {
    print('Data:$data');
    // 不再使用时,我们会关闭管道
    receivePort.close();
    // 需要将isolate杀死
    isolate?.kill(priority: Isolate.immediate);
  });
}

void foo(SendPort sendPort) {
  sendPort.send("Hello World");
}

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/125393703