内容提要
本文介绍Dart异步与并发机制:单线程事件循环处理任务,微任务队列优先于事件队列;async/await不阻塞执行;Isolate内存隔离,通过消息通信,适合CPU密集任务;Zone捕获异步错误;Future API与Stream生成器yield的用法。与Kotlin对比,Dart更强调安全,避免共享状态竞争。
延伸解读
事件循环与队列优先级
Dart 的事件循环维护两个队列:微任务队列和事件队列。微任务队列优先级更高,每次事件处理完后会清空微任务队列。理解这一点有助于排查 Future 执行顺序问题。例如,Future(() => ...) 进入事件队列,而 Future.microtask() 进入微任务队列,因此后者先执行。
Isolate 与线程的区别
Isolate 不是线程,每个 Isolate 拥有独立内存,不共享状态,只能通过消息传递通信。这避免了内存级数据竞争,但无法完全消除逻辑竞态。与 Kotlin 协程共享内存不同,Dart 牺牲灵活性换取安全性。适合 CPU 密集任务,而 I/O 等待用 async/await 即可。
Zone 的全局错误捕获
Zone 是异步代码的执行上下文,可以捕获 Zone 内所有未处理的异步错误,相当于全局 try-catch。Flutter 框架内部使用 Zone 包裹应用,通过 FlutterError.onError 等机制接管异常。与 Android 的 Thread.setDefaultUncaughtExceptionHandler 相比,Zone 能力更强,可拦截和定制异步行为。
yield 与 Kotlin 的差异
Dart 的 yield 用于向 Stream 产出值并挂起,与 Kotlin sequence 中的 yield 或 Flow 中的 emit 类似。但 Kotlin 协程中还有一个无参 yield(),用于让出执行权,与 Dart 的 yield 语义完全不同。理解这一差异有助于避免混淆。
Q&A
Dart 为什么是单线程的?它如何处理并发?
Dart 代码默认运行在单个 isolate 的单个线程上,通过事件循环(Event Loop)处理并发。事件循环维护两个队列:微任务队列和事件队列,按顺序处理事件。异步操作(如网络请求)由运行时或操作系统处理,完成后将结果作为事件放入队列,事件循环再执行回调,从而实现非阻塞的并发。
Dart 中微任务队列和事件队列有什么区别?执行顺序是怎样的?
微任务队列优先级高于事件队列。事件循环会先清空微任务队列中的所有任务,然后从事件队列中取出一个事件处理,处理完后再检查微任务队列。微任务通常由 scheduleMicrotask() 或 Future.microtask() 创建,而事件队列中的任务由 Future() 或 Timer 等创建。
Dart 中的 async/await 是如何工作的?它真的不阻塞线程吗?
async 函数执行到第一个 await 表达式时会暂停,并立即返回一个 Future,不会阻塞 isolate 的执行流。await 期间,事件循环可以继续处理其他事件。底层异步操作(如文件 I/O、网络请求)由操作系统执行,完成后将结果作为事件放入队列,事件循环再恢复 async 函数的执行。
Dart 中的 Zone 是什么?它有什么作用?
Zone 是 Dart 中异步代码的执行上下文,可以理解为沙箱。它允许拦截和定制异步行为,最重要的是可以捕获该 Zone 内所有未处理的异步错误。通过 runZoned() 可以创建子 Zone,并设置 onError 回调来捕获异常,避免程序崩溃。Flutter 框架内部大量使用 Zone 来管理错误。
Dart 的 Isolate 和线程有什么区别?为什么说 Isolate 更安全?
Isolate 拥有独立的内存和事件循环,不与其他 Isolate 共享状态,只能通过消息传递通信。因此不存在内存级别的数据竞争,不需要锁机制,更安全。而线程共享内存,需要同步机制避免竞态。Isolate 不是线程,它更接近 Actor 模型。
在 Dart 中,什么时候应该使用 Isolate?
当有 CPU 密集型计算(如大图滤镜、复杂算法、加密)可能阻塞事件循环时,应该使用 Isolate。对于 I/O 等待(如读文件、网络请求),使用 async/await 即可,因为底层是非阻塞的。判断标准是计算量是否大到会暂时阻塞其他计算。
Dart 中 Future() 和 Future.value() 的执行顺序有什么不同?
Future() 的回调进入事件队列,而 Future.value() 创建的是已完成的 Future,其 .then() 回调进入微任务队列。因此,微任务队列中的回调会先于事件队列中的回调执行。例如,Future(() => print('A')) 和 Future.value(null).then((_) => print('B')),输出顺序是 B 先于 A。
Dart 中广播流(Broadcast Stream)和普通流(冷流)有什么区别?
普通流(冷流)只能有一个监听者,且只有在有消费者时才执行。广播流(热流)允许多个监听者同时监听,数据会推送给所有监听者。广播流可以通过 StreamController.broadcast() 或 asBroadcastStream() 创建。广播流不缓存历史数据,新监听者只能收到加入之后的数据。
Dart 中 yield 和 Kotlin 中的 yield 有什么不同?
Dart 的 yield 用于 async* 函数中向 Stream 产出值并挂起,等待消费者读取下一个值时恢复。Kotlin 中 yield 有两种语义:在 sequence 生成器中产出值(类似 Dart 的 yield),在协程中无参 yield() 用于让出执行权给调度器。Dart 的 yield 专注于产出值,而 Kotlin 的 yield 根据上下文有不同含义。