Dart 基本语法(事件循环机制篇)


Dart 的事件循环

异步的功能存放在dart:async中,里面有多个子类:

Dart 基本语法(事件循环机制篇)

从 Dart 2.1 开始,使用 Future 和 Stream 不需要导入 dart:async ,因为 dart:core 库 export 了这些类。

在Dart的设计中,异步是通过 Event Loop 机制处理的。如果仅是正常使用,本篇过于深入,但是了解一下有助于理解 Dart。

为了更加深入理解 Dart 的机制,参考 Dart 官方 2013 年的一篇文章The Event Loop and Dart

基本概念

事件循环和队列

事件循环的工作是从事件队列中获取一个项目并对其进行处理,只要队列有项目,就重复这两个步骤。

Dart 基本语法(事件循环机制篇)

队列中的项可能表示用户输入、文件 I/O 通知、计时器等。例如,下面是包含计时器和用户输入事件的事件队列的图片:

Dart 基本语法(事件循环机制篇)

在应用中,所有的 Dart 代码都在 isolate 中运行。每一个 Dart 的 isolate 都有独立的运行线程,它们无法与其他 isolate 共享可变对象。在需要进行通信的场景里,isolate 会使用消息机制。尽管 Dart 的 isolate 模型设计是基于操作系统提供的进程和线程等更为底层的原语进行设计的。

大部分 Dart 应用只会使用一个 isolate(即 主 isolate),同时你也可以创建更多的 isolate,从而在多个处理器内核上达成并行执行代码的目的。

Isolate 的工作原理

Dart 代码并不在多个线程上运行,取而代之的是它们会在 isolate 内运行。每一个 isolate 会有自己的堆内存,从而确保 isolate 之间互相隔离,无法互相访问状态。由于这样的实现并不会共享内存,所以你也不需要担心 互斥锁和其他锁。

在使用 isolate 时,你的 Dart 代码可以在同一时刻进行多个独立的任务,并且使用可用的处理器核心。 Isolate 与线程和进程近似,但是每个 isolate 都拥有独立的内存,以及运行事件循环的独立线程。

主 isolate

在一般场景下,你完全无需关心 isolate。通常一个 Dart 应用会在主 isolate 下执行所有代码,如下图所示:

Dart 基本语法(事件循环机制篇)

就算是只有一个 isolate 的应用,只要通过使用 async-await 来处理异步操作,也完全可以流畅运行。一个拥有良好性能的应用,会在快速启动后尽快进入事件循环。这使得应用可以通过异步操作快速响应对应的事件。

Isolate 的生命周期

如下图所示,每个 isolate 都是从运行 Dart 代码开始的,比如 main() 函数。执行的 Dart 代码可能会注册一些事件监听,例如处理用户操作或文件读写。当 isolate 执行的 Dart 代码结束后,如果它还需要处理已监听的事件,那么它依旧会继续被保持。处理完所有事件后,isolate 会退出。

Dart 基本语法(事件循环机制篇)

事件处理

在客户端应用中,主 isolate 的事件队列内,可能会包含重绘的请求、点击的通知或者其他界面事件。例如,下图展示了包含四个事件的事件队列,队列会按照先进先出的模式处理事件。

Dart 基本语法(事件循环机制篇)如下图所示,在 main() 方法执行完毕后,事件队列中的处理才开始,此时处理的是第一个重绘的事件。而后主 isolate 会处理点击事件,接着再处理另一个重绘事件。

Dart 基本语法(事件循环机制篇)如果某个同步执行的操作花费了很长的处理时间,应用看起来就像是失去了响应。在下图中,处理点击事件的代码比较耗时,导致紧随其后的事件并没有及时处理。这时应用可能会产生卡顿,所有的动画都无法流畅播放。

Dart 基本语法(事件循环机制篇)在一个客户端应用中,耗时过长的同步操作,通常会导致 卡顿的动画。而最糟糕的是,应用界面可能完全失去响应。

后台运行对象

如果你的应用受到耗时计算的影响而出现卡顿,例如 解析较大的 JSON 文件,你可以考虑将耗时计算转移到单独工作的 isolate,通常我们称这样的 isolate 为 后台运行对象。下图展示了一种常用场景,你可以生成一个 isolate,它将执行耗时计算的任务,并在结束后退出。这个 isolate 工作对象退出时会把结果返回。

Dart 基本语法(事件循环机制篇)

每个 isolate 都可以通过消息通信传递一个对象,这个对象的所有内容都需要满足可传递的条件。并非所有的对象都满足传递条件,在无法满足条件时,消息发送会失败。举个例子,如果你想发送一个 List<Object>,你需要确保这个列表中所有元素都是可被传递的。假设这个列表中有一个 Socket,由于它无法被传递,所以你无法发送整个列表。

你可以查阅 send() 方法 的文档来确定哪些类型可以进行传递。

Isolate 工作对象可以进行 I/O 操作、设置定时器,以及其他各种行为。它会持有自己内存空间,与主 isolate 互相隔离。这个 isolate 在阻塞时也不会对其他 isolate 造成影响。

实现一个简单的 isolate 工作对象

本节将展示一个主 isolate 与它生成的 isolate 工作对象的实现。 Isolate 工作对象会执行一个函数,完成后结束对象,并将函数结果发送至主 isolate。(Flutter 提供的 compute() 方法 也是以类似的方式工作的。)

下面的示例将使用到这些与 isolate 相关的 API:

  • Isolate.spawn() 和 Isolate.exit()
  • ReceivePort 和 SendPort

主 isolate 的代码如下:

  void main() async {
  // 读取数据
  final jsonData = await _parseInBackground();

  // 使用数据
  print('Number of JSON keys: ${jsonData.length}');
}

// 生成一个isolate并等待消息返回
Future<Map<Stringdynamic>> _parseInBackground() async {
  final p = ReceivePort();
  await Isolate.spawn(_readAndParseJson, p.sendPort);
  return await p.first as Map<Stringdynamic>;
}

_parseInBackground() 方法包含了 生成 后台 isolate 工作对象的代码,并返回结果:

  1. 在生成 isolate 之前,代码创建了一个 ReceivePort,让 isolate 工作对象可以传递信息至主 isolate。
  2. 接下来是调用 Isolate.spawn(),生成并启动一个在后台运行的 isolate 工作对象。该方法的第一个参数是 isolate 工作对象执行的函数引用:_readAndParseJson。第二个参数则是 isolate 用来与主 isolate 传递消息的 SendPort。此处的代码并没有 创建 新的 SendPort,而是直接使用了 ReceivePort 的 sendPort 属性。
  3. Isolate 初始化完成后,主 isolate 即开始等待它的结果。由于 ReceivePort 实现了 Stream,你可以很方便地使用 first 属性获得 isolate 工作对象返回的单个消息。

初始化后的 isolate 会执行以下代码:

Future<void> _readAndParseJson(SendPort p) async {
  final fileData = await File(filename).readAsString();
  final jsonData = jsonDecode(fileData);
  Isolate.exit(p, jsonData);
}

在最后一句代码后,isolate 会退出,将 jsonData 通过传入的 SendPort 发送。在 isolate 之间传递消息时,通常会发生数据拷贝,然而,当你使用 Isolate.exit() 发送数据时, isolate 中持有的消息并没有发生拷贝,而是直接转移到了接收的 isolate 中。

下图说明了主 isolate 和 isolate 工作对象之间的通信流程:

Dart 基本语法(事件循环机制篇)

事件循环和队列

Dart 应用程序具有一个包含两个队列的事件循环 – 事件队列(Event quque)和微任务队列(MicroTask quque)。

事件队列包含所有外部事件:I/O、鼠标事件、绘制事件、计时器、Dart 隔离之间的消息等。

微任务队列是必需的,因为事件处理代码有时需要在稍后完成任务,但在将控制权返回给事件循环之前。例如,当一个可观察的对象发生变化时,它会将几个突变变化组合在一起,并异步报告它们。微任务队列允许可观察对象在 DOM 显示不一致状态之前报告这些突变更改。

如下图所示,当 main() 退出时,事件循环开始工作。首先,它按FIFO顺序执行任何微任务。然后,它取消排队并处理事件队列上的第一项。然后它重复这个循环:执行所有微任务,然后处理事件队列上的下一项。一旦两个队列都为空并且预期没有更多事件,应用的嵌入程序(如浏览器或测试框架)就可以释放应用。

Dart 基本语法(事件循环机制篇)

重要:当事件循环执行微任务队列中的任务时,事件队列会卡住:应用程序无法绘制图形、处理鼠标单击、对 I/O 做出反应等。

Dart 基本语法(事件循环机制篇)

在其中,有一些常用方法如下:

  • new Future(fun):(异步)添加到Event队列
  • Future.delayed():(异步)执行完延迟后,添加到Event队列
  • Future.error(error,stackTrace):创建一个带错误的future
  • Future.microtask(fun):(异步)添加到MicroTask队列
  • scheduleMicrotask(fun):(异步)添加到MicroTask队列
  • Future.sync(fun):(同步)
  • Future.value(value):(异步)添加到Event队列

原文始发于微信公众号(anywareAI):Dart 基本语法(事件循环机制篇)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/186663.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!