谈到异步,可能大家多会想到多线程,然而Dart是基于事件循环机制
的单线程模型
。
单线程?嗯哼,也就是说在Dart的世界里没有多线程之说,当然也没有了所谓的主线程和子线程之分。
那么,Dart是如何实现异步的呢?本文就以下介绍流程帮助你认识Dart异步。
graph LR
A[同步与异步]-->B[Dart事件循环机制]
B-->C[Isolate设计]
同步与异步
同步
同一线程中,按照代码的编写顺序,自上而下依次执行。不过,在遇到如网络请求、IO等耗时操作时会造成线程阻塞,后面的代码迟迟得不到执行的问题。
对于耗时操作带来的问题,我们很容易想到用异步去解决。Dart也是这样吗?不要被这一问蒙蔽了你纯真的双眼,当然是啊,小仙女。
异步
代码执行中,某段代码的执行并不会影响后面代码的执行。
说得好像有点抽象,emm.... 让我们看看异步的实现方式~
-
多线程
开启另一条线程执行一段耗时代码,这样两条线程可以并列执行,自然不会阻塞线程而影响后面代码的执行了。
多线程在适量并合理地使用下,可以说真香。但是其缺点也是显而易见的:
- 开启线程会带来额外的资源和性能消耗,在遇到大量并发时,会给服务器带来极大的压力。
- 多个线程操作共享内存时需要加锁控制,锁竞争会降低性能和效率,复杂情况下还容易造成死锁。
- 单线程模型
一条执行线上,同时且只能执行一个任务(事件),其他任务都必须在后面排队等待被执行。也就是说,在一条执行线上,为了不阻碍代码的执行,每遇到的耗时任务都会被挂起放入任务队列,待执行结束后再按放入顺序依次执行队列上的任务,从而达到异步效果。
上述执行线为什么不直接说是线程?因为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
- 外部事件
例如:输入/输出,手势,绘制,计时器,Stream等等;
对于外部事件
,一旦没有任何microtask要执行,Event looper就会考虑将列为队列中的第一项并执行它。
- 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
- 上文已述,Microtask queue的优先级要高于Event queue
-
使用场景:想要在稍后完成一些任务(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本身就是隔离的意思...
许多Dart应用都在单个isolate
中运行所有代码,但是如果特殊需要,您可以拥有多个。
Isolate
间可以一起工作的唯一方法是通过来回传递消息。一个isolate
将消息发送到另一个isolate
,接收者使用其Event looper
处理该消息。
最后唠叨
一个Dart应用是从Main isolate的main函数开始的,main函数执行完后,Event looper
开始工作 ... 等等!停车!停车!为啥这么眼熟呢? 嗯哼,不可说,不可说~~~
参考资料
-
Android单线程模型和JavaScript单线程模型
- 最新
- 最热
只看作者