Dart 异步模型讲清楚:事件循环、任务队列与使用边界

橘子海
橘子海
发布时间:2026-08-25| 103人浏览过
原创

前言

Dart 的异步模型常被说成“事件循环加任务队列”,但真正写代码时,很多人还是会困惑:为什么某段回调先执行,为什么异步写法仍可能卡住主线程。本文从单线程的限制讲起,拆开微任务、事件任务和事件循环的执行顺序,再说明遇到耗时逻辑时该如何判断是拆分任务还是交给 Isolate。

Dart 的异步模型看起来抽象,真正难点其实不在名词,而在弄清它解决了什么问题、靠什么机制运转。本文会从单线程的局限讲起,再拆开事件循环与两类任务队列的执行顺序,最后结合常见误区说明什么时候该用微任务、什么时候该考虑 Isolate,帮助你判断异步代码为什么会这样执行。

为什么 Dart 需要异步模型

Dart 引入异步模型,核心是同时解决两件事:一是程序里确实存在大量耗时操作,二是 Dart 本身采用单线程执行模型。

异步要解决哪些耗时场景

所谓异步操作,本质上是让耗时任务不要把后续代码全部卡住。也就是说,某个操作还没完成时,程序可以先继续往下执行,而不是原地等待。

常见的耗时操作主要包括:

  • 网络请求:如 Http 请求。
  • 文件操作:如读写文件、数据库操作。
  • 图像处理:如格式处理。

如果这些任务都按同步方式串行等待,界面响应、逻辑调度和整体吞吐都会明显变差。

单线程模型的局限在哪里

Dart 的单线程意味着任务默认按顺序执行,并且同一时刻只能在主线程上处理一件事。这样的设计有明确好处,但也有天然边界。

它的局限主要体现在两点:

  • 不能充分利用资源:单线程只使用一核 CPU,无法直接吃满多核能力。
  • 耗时任务会阻塞整个线程:一旦当前代码里出现耗时操作,后面的任务就只能继续等待。

也正因为如此,Dart 需要一套机制,让耗时操作不直接卡住主线程,同时又不把语言模型变成传统多线程那种高同步成本的形态。

Dart 异步模型是怎么工作的

Dart 的异步模型建立在事件循环 + 任务队列之上。简单说,耗时 I/O 可以交给操作系统去执行,主线程不必一直阻塞等待;而当结果返回后,事件循环再负责把对应回调安排回主线程执行。

这也是 Dart 在保持单线程编程体验的同时,实现异步并发的关键。

先理解:Dart 仍然是单线程语言

Dart 是单线程语言,所有任务默认都由主线程,也就是主 Isolate 负责执行。

这种选择的直接优势是:顺序执行的语义更稳定,也天然避开了多线程常见的资源竞争、锁以及死锁问题。换句话说,Dart 没有靠共享内存多线程去解决大部分并发问题,而是优先通过事件循环来组织任务。

Dart 如何在单线程前提下实现并发和并行,可以分开理解:

  • 并发:通过事件循环机制实现异步调度。
  • 并行:通过开辟多个Isolate实现并行计算。

前者解决的是“主线程如何不被轻易阻塞”,后者解决的是“如何利用多核去跑真正适合并行的任务”。

事件循环与两类任务队列

Dart 的事件循环负责调度和执行任务。它并不是让多个任务在同一时刻真的一起跑,而是通过交错安排执行顺序,实现单线程下的并发效果。

这个模型里最关键的是两类任务队列:

  • 微任务队列:优先级更高,适合放短小、需要尽快执行的后续任务。
  • 事件任务队列:优先级低于微任务,常见的异步事件会排在这里等待处理。

调度规则很直接:优先级高的先执行。

真正需要记住的是循环执行规则:

  1. 先检查微任务队列是否为空。
  2. 如果不为空,先把微任务队列中的任务全部执行完。
  3. 微任务清空后,再从事件任务队列里取出第一个事件任务执行。
  4. 当前事件任务执行结束后,再重新回到第一步,继续检查微任务队列。
  5. 当两个队列都没有任务时,事件循环进入等待状态,直到有新事件到来。

这意味着一个重要事实:事件任务并不是一到就立刻执行,它必须等当前轮次的微任务全部处理完,才有机会进入执行阶段。

void eventLoop() {
    while (true) {
        if (微任务队列不为空) {
            执行微任务队列中所有微任务;
        } else if (事件队列不为空) {
            处理事件任务队列中的第一个事件任务;
        } else {
            等待新的事件;
        }
    }
}

可以把它理解成一种固定节奏:先清空高优先级任务,再处理一个低优先级事件,然后重新判断。Dart 的异步行为之所以常常“看起来和直觉不一致”,大多就是因为代码实际进入的是不同队列。

使用 Dart 异步模型时要注意什么

理解事件循环之后,更重要的是知道哪些认识是对的,哪些是常见误解。

Dart 异步实践中的任务选择图
遇到耗时任务时该怎么选把微任务、事件任务和 Isolate 的使用边界放在同一张对比图里。

微任务优先级高,不等于执行更快

微任务并不是“性能更高”的任务,它只是优先级更高。因此它会先于事件任务执行,但这不代表它更适合承载耗时工作。

通常微任务应该是比较小、比较快的任务。如果把耗时逻辑塞进大量微任务里,就会导致事件任务长期得不到执行,也就是常说的“事件任务饥饿”。一旦出现这种情况,程序响应会变差,异步调度反而会失去意义。

同步耗时代码仍然会阻塞事件循环

很多人以为只要项目里用了异步模型,就不会再卡线程。实际上不是这样。

如果同步代码里本身存在耗时操作,例如某些 I/O 密集型逻辑,事件循环一样会被阻塞。原因很简单:同步代码会先执行,而主线程一旦被这段逻辑占住,后续的微任务和事件任务都无法被调度。

遇到这类问题,通常有两种处理思路:

  • 如果任务可以拆开,就拆分为多个较小任务,再交给微任务调度。
  • 如果任务不适合拆分,或者本身就是计算量较大的工作,则考虑使用 Isolate 做并行计算。

这个判断很关键,因为异步模型解决的是“调度问题”,不是把所有重任务自动变轻。

性能与 Isolate 的边界怎么理解

Dart 虽然是单线程,但并不意味着性能一定弱。文中给出的测试数据是:Dart VM 可每秒处理 20 多万个微任务事件,事件任务处理也能满足常规需求。

因此,大多数业务场景下,不需要因为“单线程”三个字就过度担心性能。真正需要关注的是任务类型是否放对位置:

  • 短小、需要快速衔接的逻辑,适合微任务。
  • 常规异步事件,按事件队列调度即可。
  • 无法拆分的重计算或希望利用多核 CPU 的任务,则应考虑多个 Isolate。

换句话说,Dart 不是不能利用多核,而是默认执行模型不直接依赖多线程共享状态;当你确实需要并行能力时,可以通过开辟多个 Isolate 来完成。

总结:把握执行顺序,异步模型就不再抽象

Dart 异步模型的理解重点,不在背概念,而在抓住三件事。

  • Dart 之所以需要异步,是因为耗时操作真实存在,而单线程会被阻塞。
  • Dart 通过事件循环 + 任务队列组织异步执行,其中微任务队列优先级高于事件任务队列。
  • 异步不等于万能提速。同步耗时代码仍会卡住主线程;可拆分任务可分批调度,不可拆分的重任务则更适合交给 Isolate。

只要你能判断一段代码进入了哪个队列、什么时候会轮到它执行、是否会阻塞主线程,Dart 的异步行为基本就能解释清楚了。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。