Dart 异步编程:Isolates and event loops

最近在看 Flutter 的 Future,翻到这篇文章,文章写地挺不错的,虽然没有涉及到什么深度,但作为一篇 Dart 异步编程的基础知识可好。

文章来源于 Medium,文章在我的理解基础上做了些加工,英文好的朋友可以直接去读原文

Dart,尽管是一种单线程语言,但它提供了对 Future、Streams、后台工作的支持,以及提供了所有需要以现代、异步和反应(例如Flutter)等方式编写的东西的支持。这篇文章介绍了支持后台工作的基础:Isolates and event loops (隔离和时间循环)。

那么我们开始介绍 isolates 吧!

Isolates

所有的 dart 代码都运行在一个 isolate 中,这就像机器上的一个小空间,每个 ioslate 都拥有私有的内存块和一个运行着 event loop 的单一线程。

1_isolate_single.png

如上图,绿色的方块表示 isolate 私有的内存块,红色的循环表示 isolate 的 event loop。

在例如 C ++ 的许多其他编程语言中,它们允许多个线程共享相同的内存并运行您想要的任何代码。然而在 Dart 中这是不允许的,在Dart中,每个线程都有自己的内存,并且线程只处理事件(稍后会详细介绍)。

许多Dart应用程序在一个单独的 isolate 中运行所有的代码,但你需要的话,一个应用也可以有多个 isolate。如果你在主 isolate 中有一个计算量非常大的任务在运行,可能会导致你的应用掉帧。因为在 Dart 中应用的io、计时器、点击以及绘制的事件都是以 event 的形式在 event loop中排队运行,而 event loop 是在主 isolate 的线程空闲时才会进行遍历。如果在主 isolate 中执行计算量大的操作,会阻塞 event loop 的执行。

因此你可以使用Isolate.spawn()Flutter's compute()函数,这两个函数会另外创建了一个与主 isolate 分开的 子 isolate,在子 isolate 中执行繁重的计算操作,让你的主 isolate 时刻空闲,自由地重建和呈现 widget 树。

2_isolate_two.png

新建的子 isolate ,有自己的 event loop 和私有内存, 虽然它由主 isolate 创建,但主 isolate 依然不允许访问。这也是 isolate 名字的来源:这些小空间彼此 isolate (隔离)。

事实上,不同 isolates 之间一起工作的唯一方式,是通过反复不停的传递消息来实现的。一个 isolate 向另一个 isolate 发送消息,后者接受到消息后,将消息放入自己 event loop 执行。

3_isolate_msg.png

对于 Java or C++ 开发者来说,缺少内存的共享可能听起来有点严格,但不共享内存还是有一些关键好处的。

例如,在一个单独的 isolate 中进行内存的分配和垃圾回收是不需要将内存锁住的。一个 isolate 只有一个线程,如果线程不繁忙,那你就知道内存没有被改变(而多线程之间共享内存有可能会导致内存产生脏数据,进而有引发死锁的可能性)。这对于 flutter apps 需要快速构建和拆卸一堆的widgets来说很有好处,因为不需要关心内存数据是否已经发生变化,

Event loops

现在你已经有了对 isolates 的基础知识,接下来让我们深入了解异步代码发生的真正原因: event loop(事件循环)。

想象将一个 app 的生命周期延伸成一条时间线,一个 app 从开始运行到停止的过程,期间会发生许多事件,例如硬盘的 I/O ,用户手指的按下等等诸如此类的操作。

你的 app 无法预测这些事件什么时候以什么顺序发生,这些事件到来时还需要在单线程内完成处理。所以 app 会维护一个 event loop,app 会以先进先出的顺序从 event queue 中取出事件并执行它,然后执行下一个,以此类推直到 event queue 队空。

整个程序运行的时候,可能会发生这些动作:你在点击屏幕,东西在下载,计时器会响,event loop 会不断循环,一次一个处理这些事件

4_event_loop.png

如果在事件处理一个动作时发生的中断,那么线程会挂起,等待下一个事件的到来。等待时可以触发 GC 等内存维护的工作。

dart 中所有为异步编程准备的高级 APIs 和语言特性——futures,streams,async and await,都是建立在这个 event loop的基础上运行的。

例如,假设您有一个启动网络请求的按钮,就像下面这个:

RaisedButton(
  child: Text('Click me'),
  onPressed: () {
    final myFuture = http.get('https://example.com');
    myFuture.then((response) {
      if (response.statusCode == 200) {
        print('Success!');
      }
    });
  },
)

当你运行在你的 app,Flutter 会构建在屏幕上构建一个按钮,然后你的 app 进入等待。

你的 app event loop 像进入了空闲 idle 状态,等待着下一个事件的到来,在等待用户点击按钮时,其他与按钮不相关的事件可能会到来并被执行。最终用户点击的按钮,一个点击事件进入到 event queue 中。

点击事件被取出并执行,Flutter 的渲染系统会检查,发现”这个点击事件发生的位置与按钮相匹配“,于是 Flutter 会找到这个位置的按钮,并执行按钮的 onPressed() 函数,如上所示,在 onPressed() 函数中,回启动一个网络请求(网络请求返回一个未完成的 Future 对象),并使用then()给这个 Future 注册一个完成时的 handler。

就这样,event loop 完成了对点击事件的处理,然后将这个点击事件丢掉。

现在,onPressed 是 RaisedButton 的一个属性,网络请求则用了一个 Future 的 callback 回调。这两种技巧在做同一件事,都在告诉 Flutter:”嘿,过一会会有一个指定类型的事件到来,如果来了,请执行我指定的这一段代码。“

就这样,onPressed 在等待一个点击事件,而 future 则在等待网络数据,从 Dart 的视角来看,他们都只不过是队列中等待的事件罢了。

这就是 Dart 中异步编程的运行方式。Futures,streams,async and await,这些 APIs 只是提供了让你告知 Dart 的 event loop的方式——”这里有一段代码,请在一会后运行。“

如果我们回头看看代码示例,您现在可以确切地看到它是如何为特定的事件分解为块的。有初始构建(1)、点击事件(2),和网络响应事件(3)。

RaisedButton( // (1)
  child: Text('Click me'),
  onPressed: () { // (2)
    final myFuture = http.get('https://example.com');
    myFuture.then((response) { // (3)
      if (response.statusCode == 200) {
        print('Success!');
      }
    });
  },
)

在您习惯了使用异步代码之后,您将开始识别这些模式。理解事件循环将有助于您进一步了解更高级别的 API。

总结

我们快速地了解了隔离、事件循环和Dart中异步编码的基础。如果您正在寻找更多的细节,如微任务队列如何工作,请参阅过时的,存档的,但仍然受喜爱的文章 The Event Loop and Dart

要了解更多关于Dart中的异步的信息,请查看本系列的下一篇文章 Dart asynchronous programming: Futures

兄dei,如果觉得我写的还不错,麻烦帮个忙呗 😃
  1. 给俺点个赞被,激励激励我,同时也能让这篇文章让更多人看见,(#.#)
  2. 不用点收藏,诶别点啊,你怎么点了?这多不好意思!
  3. 噢!还有,我维护了一个路由库。。没别的意思,就是提一下,我维护了一个路由库 =.= !!

拜托拜托,谢谢各位同学!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值