发布时间:2019年7月26日 - 5分钟阅读
尽管Dart是一种单线程语言,但它提供了对期货、流、后台工作的支持,以及以现代的、异步的、(在Flutter的情况下)反应式的方式编写所需的所有其他东西。本文介绍了Dart对后台工作的支持基础:隔离和事件循环。 如果你喜欢通过看或听来学习,本文中的所有内容都会在下面的视频中涉及,这是Flutter in Focus视频系列Asynchronous Programming in Dart的一部分。
还在这里?让我们来谈谈隔离物。
隔离物
隔离区是所有Dart代码运行的地方。它就像机器上的一个小空间,有自己的私有内存和一个运行事件循环的单线程。
一个隔离区有自己的内存和一个运行事件循环的执行线程。
在很多其他语言中,比如C++,你可以让多个线程共享同一个内存,运行任何你想要的代码。但在Dart中,每个线程都在自己的隔离区,有自己的内存,线程只是处理事件(稍后再谈)。
许多Dart应用程序在一个隔离区中运行所有的代码,但如果你需要的话,你可以有多个隔离区。如果你要执行的计算非常巨大,如果在主隔离中运行可能会导致你掉帧,那么你可以使用Isolate.spawn()或Flutter的compute()函数。这两个函数都会创建一个单独的隔离区来进行数字运算,让你的主隔离区可以自由地--比如说--重建和渲染widget树。
两个隔离体,每个隔离体都有自己的内存和执行线程。
新的隔离体有自己的事件循环和自己的内存,而原来的隔离体--尽管它是新隔离体的父体--不允许访问这些内存。这就是隔离体这个名字的来源:这些小空间被保持相互隔离。
事实上,隔离体能够一起工作的唯一方式是通过来回传递消息。一个隔离体向另一个隔离体发送消息,接收的隔离体使用它的事件循环处理该消息。
隔离体可以向其他隔离体发送消息。
这种共享内存的缺乏可能听起来有点严格,特别是当你来自于像Java或C++这样的语言时,但它对Dart编码者有一些关键的好处。
例如,隔离区中的内存分配和垃圾收集不需要锁定。只有一个线程,所以如果它不忙,你知道内存没有被突变。这对于Flutter应用来说是很好的,因为它有时需要快速建立和拆卸一堆部件。
事件循环
现在你已经对隔离器有了一个基本的介绍,让我们深入了解一下真正使异步代码成为可能的东西:事件循环。
想象一下,一个应用的生命在时间轴上延伸出来。应用启动,应用停止,中间发生了一堆事件--来自磁盘的I/O,用户的手指点击......各种各样的事情。
你的应用无法预测这些事件何时发生或顺序如何,它必须用一个从不阻塞的单线程来处理所有这些事件。所以,应用程序运行一个事件循环。它从它的事件队列中抓取最老的事件,处理它,再回去处理下一个,处理它,以此类推,直到事件队列清空。
在应用运行的整个过程中--你在点击屏幕,东西在下载,定时器响起--事件循环就在不停地循环,一次一次地处理这些事件。
事件循环一次处理一个事件。
当动作中断的时候,线程就会挂起,等待下一个事件。它可以触发垃圾收集器,去喝咖啡,等等。 Dart为异步编程提供的所有高级API和语言特性--期货、流、异步和等待--它们都建立在这个简单的循环之上,并围绕着这个简单的循环。
例如,假设你有一个启动网络请求的按钮,就像这个按钮。
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
当你运行你的应用时,Flutter会建立这个按钮,并把它放在屏幕上。然后你的应用就会等待。 你的应用程序的事件循环只是有点闲置,等待下一个事件。与按钮无关的事件可能会进来并得到处理,而按钮则坐在那里等待用户点击它。最终他们点了,一个点击事件进入队列。
这个事件被接收到处理。Flutter看了看,渲染系统说:"这些坐标与升起的按钮相匹配",所以Flutter执行了onPressed函数。这段代码启动了一个网络请求(它返回一个future),并通过使用then()方法为future注册一个完成处理程序。
就是这样。循环完成了对该tap事件的处理,并将其丢弃。
现在,onPressed是RaisedButton的一个属性,而网络事件使用的是Future的回调,但这两种技术都在做同样的基本事情。它们都是告诉Flutter,"嘿,以后,你可能会看到一个特定类型的事件进来。当你看到的时候,请执行这段代码。"
所以,onPressed是在等待敲击,而未来是在等待网络数据,但从Dart的角度来看,这些都只是队列中的事件。
而这就是Dart中异步编码的工作原理。未来、流、异步和等待--这些API都只是让你告诉Dart的事件循环:"这里有一些代码,请稍后运行它"。
如果我们回头看一下这个代码示例,你现在可以看到它是如何被分解成特定事件的块的。有初始构建(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中异步编码的基础。如果你想了解更多的细节,比如微任务队列是如何工作的,请看那篇已经过时的、已经归档的、但仍然很受欢迎的文章《事件循环与Dart》。
要了解更多关于Dart中异步的内容,请查看本系列的下一篇文章《Dart异步编程:Future》。
非常感谢Andrew Brogdon,他创建了本文所基于的视频。