Flutter开发之Dart线程与异步

谈到异步,可能你们多会想到多线程,然而Dart是基于事件循环机制单线程模型node

单线程?嗯哼,也就是说在Dart的世界里没有多线程之说,固然也没有了所谓的主线程和子线程之分。web

那么,Dart是如何实现异步的呢?本文就如下介绍流程帮助你认识Dart异步。服务器

同步与异步
Dart事件循环机制
Isolate设计

同步与异步

同步

同一线程中,按照代码的编写顺序,自上而下依次执行。不过,在遇到如网络请求、IO等耗时操做时会形成线程阻塞,后面的代码迟迟得不到执行的问题。网络

对于耗时操做带来的问题,咱们很容易想到用异步去解决。Dart也是这样吗?不要被这一问蒙蔽了你纯真的双眼,固然是啊,小仙女。多线程

异步

代码执行中,某段代码的执行并不会影响后面代码的执行。并发

说得好像有点抽象,emm… 让咱们看看异步的实现方式~异步

  • 多线程

开启另外一条线程执行一段耗时代码,这样两条线程能够并列执行,天然不会阻塞线程而影响后面代码的执行了。async

多线程在适量并合理地使用下,能够说真香。可是其缺点也是显而易见的:svg

  1. 开启线程会带来额外的资源和性能消耗,在遇到大量并发时,会给服务器带来极大的压力。
  2. 多个线程操做共享内存时须要加锁控制,锁竞争会下降性能和效率,复杂状况下还容易形成死锁。
  • 单线程模型

一条执行线上,同时且只能执行一个任务(事件),其余任务都必须在后面排队等待被执行。也就是说,在一条执行线上,为了避免阻碍代码的执行,每遇到的耗时任务都会被挂起放入任务队列,待执行结束后再按放入顺序依次执行队列上的任务,从而达到异步效果。函数

上述执行线为何不直接说是线程?由于Dart没有线程概念,只有Isolate。因此,本文命题(Flutter…Dart线程…)就是错的,目的就是引发童鞋们的注意。固然,在此方面理解为线程,甚至理解为一个函数体也没毛病…

单线程模型的优点就是避免了上述多线程的缺点,然而这种方式比较适合于每每把时间浪费在等待对方传送数据或者返回结果的耗时操做,如网络请求,IO流操做等。对于尽量利用处理器的多核实现并行计算的计算密集型操做相对来讲多线程更为合适。

Dart事件循环机制

上文说了Dart是基于事件循环机制单线程模型,那么问题来了…

为何要采用单线程模型?

App使用过程当中,多数时间处于空闲状态,并不须要进行密集或高并发的处理,计算以及UI渲染,多线程方式显然有些多余~~~

为何要基于事件循环机制?

对于用户点击,滑动,硬盘IO访问等等事件,你不知道什么时候发生或以什么顺序发生,因此得有一个永不停歇且不能阻塞的循环来等待并处理这些“突发”事件。

基于以上,基于事件循环机制的单线程模型孕育而生 ↓↓↓

Dart事件循环机制是怎样的?

Dart事件循环机制是由一个 消息循环(Event looper) 和两个消息队列:事件队列(Event queue)微任务队列(MicroTask queue) 构成。

  • Event Looper

Dart代码的运行是从main函数开始的,main函数执行完后,Event looper开始工做,Event looper优先所有执行完Microtask queue中的event
直到Microtask queue为空时,才会执行Event queue中的event,后者为空时才能够退出循环,这里强调“能够”而不是“必定”要退出,视场景而定。

  • Event Queue

该队列事件来源于外部事件Future

  1. 外部事件

例如:输入/输出,手势,绘制,计时器,Stream等等;
对于外部事件,一旦没有任何microtask要执行,Event looper就会考虑将列为队列中的第一项并执行它。

  1. Future

用于自定义Event queue事件。

经过建立Future类实例来向Event queue添加事件:

new Future(() {
  // 事件任务
});

延时5秒后添加一个事件:

new Future.delayed(const Duration(seconds:5), () {
  // 事件任务
});

若是该任务前面有其它任务须要先执行,该任务被执行的时间会大于5秒(单线程模型的缺陷之一,不能基于时钟调度)。

这里拓展下Future的一些用法(了解下就能够了,不是本文重点):

new Future(() => doTask) // 执行异步任务
    .then((result1) => doChildTask1(result1)) // doTask执行完后的子任务,result为上个任务doTask的返回值
    .then((result2) => doChildTask2(result2)) // doChildTask1执行完后的子任务,result为上个任务doChildTask1的返回值
    .whenComplete(() => doComplete); // 当全部任务完成后的回调函数

事件任务执行完后会当即依次执行then子任务,最后执行whenComplete函数。

  • Microtask Queue
  1. 上文已述,Microtask queue的优先级要高于Event queue
  2. 使用场景:想要在稍后完成一些任务(microtask)但又但愿在执行下一个事件(event)以前执行。

Microtask通常用于很是短的内部异步动做,而且任务量很是少,若是微任务很是多,就会形成Event queue排不上队,会阻塞Event queue的执行(如,用户点击没有反应)。因此,大多数状况下优先考虑使用Event queue,整个Flutter源代码仅引用scheduleMicroTask()方法7次。

经过建立scheduleMicrotask函数来向Microtask queue添加任务:

scheduleMicrotask(() {
  // 事件任务
});

Isolate

全部的Dart代码都是在isolate中运行,它就像是机器上的一个小空间,具备本身的私有内存块和一个运行着Event looper的单个线程。正如上文强调的:Dart中没有线程的概念只有isolate

isolate具备本身的内存和运行事件循环的单个执行线程

每一个isolate都是相互隔离(独立)的,并不像线程那样能够共享内存,isolate自己就是隔离的意思…

许多Dart应用都在单个isolate中运行全部代码,可是若是特殊须要,您能够拥有多个。

两个isolate,每一个isolate都有本身的内存和执行线程

Isolate间能够一块儿工做的惟一方法是经过来回传递消息。一个isolate将消息发送到另外一个isolate,接收者使用其Event looper处理该消息。

最后唠叨

一个Dart应用是从Main isolate的main函数开始的,main函数执行完后,Event looper开始工做 … 等等!停车!停车!为啥这么眼熟呢? 嗯哼,不可说,不可说~~~

参考资料

  1. Dart asynchronous programming: Isolates and event loops

  2. Futures - Isolates - Event Loop

  3. Android单线程模型和JavaScript单线程模型