最近在看 Flutter 的 Future,翻到这篇文章,文章写地挺不错的,虽然没有涉及到什么深度,但作为一篇 Dart 异步编程的基础知识可好。
文章来源于 Medium,文章在我的理解基础上做了些加工,英文好的朋友可以直接去读原文。
Dart,尽管是一种单线程语言,但它提供了对 Future、Streams、后台工作的支持,以及提供了所有需要以现代、异步和反应(例如Flutter)等方式编写的东西的支持。这篇文章介绍了支持后台工作的基础:Isolates and event loops (隔离和时间循环)。
那么我们开始介绍 isolates 吧!
Isolates
所有的 dart 代码都运行在一个 isolate 中,这就像机器上的一个小空间,每个 ioslate 都拥有私有的内存块和一个运行着 event loop 的单一线程。
如上图,绿色的方块表示 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 树。
新建的子 isolate ,有自己的 event loop 和私有内存, 虽然它由主 isolate 创建,但主 isolate 依然不允许访问。这也是 isolate 名字的来源:这些小空间彼此 isolate (隔离)。
事实上,不同 isolates 之间一起工作的唯一方式,是通过反复不停的传递消息来实现的。一个 isolate 向另一个 isolate 发送消息,后者接受到消息后,将消息放入自己 event loop 执行。
对于 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 会不断循环,一次一个处理这些事件
如果在事件处理一个动作时发生的中断,那么线程会挂起,等待下一个事件的到来。等待时可以触发 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,如果觉得我写的还不错,麻烦帮个忙呗 😃
- 给俺点个赞被,激励激励我,同时也能让这篇文章让更多人看见,(#.#)
- 不用点收藏,诶别点啊,你怎么点了?这多不好意思!
- 噢!还有,我维护了一个路由库。。没别的意思,就是提一下,我维护了一个路由库 =.= !!
拜托拜托,谢谢各位同学!