当前位置: 首页 > news >正文

Dart 中的 Event Loop(事件循环)

 了解Dart 中的 Event Loop(事件循环) 及其原理是我们理解 Dart 异步编程的核心。

理解 Dart 的事件循环机制:

  • 同步代码首先执行。

  • 然后,微任务队列(microtask queue)中的所有任务被执行。

  • 最后,事件队列(event queue)中的一个任务被执行,然后再次检查微任务队列,如此循环。

一、核心思想:为什么需要事件循环?

Dart 是单线程的。这意味着它只有一个执行线程,同一时刻只能做一件事。如果让这个线程等待一个耗时的操作(比如网络请求),它就会被“阻塞”,在此期间无法做任何其他事情(比如渲染UI、响应点击),导致应用卡顿。

为了解决这个问题,Dart 没有采用让线程“傻等”的策略,而是使用了基于事件的异步模型。所有潜在的耗时操作(I/O、计时器等)都被“外包”给了操作系统(或其他线程)去执行。Dart 线程本身只负责管理代码执行顺序,它通过一个永不停止的循环——事件循环(Event Loop)——来调度和执行任务。

二、Event Loop 的机制:两个队列(Queue)

事件循环的核心职责是:不断地从两个先进先出(FIFO)的队列中取出任务(Message/Microtask)并执行

这两个队列是:Microtask Queue(微任务队列) 和 Event Queue(事件队列)

1. Microtask Queue(微任务队列)

  • 优先级最高

  • 用于存放非常短促、需要尽快异步执行的任务。

  • 通常由 Dart 自身内部产生,例如用于处理 Future 的完成回调、或 scheduleMicrotask 函数。

  • 在当前事件循环的末尾、在下一个事件被处理之前,会清空整个微任务队列。

2. Event Queue(事件队列)

  • 优先级低于微任务队列

  • 用于存放外部事件的任务,例如:

    • I/O 操作(网络请求、文件读写)完成的消息。

    • 用户输入事件(点击、滑动)。

    • 计时器事件(TimerFuture.delayed)。

    • 绘制事件。

  • 我们编写的异步代码(如 then 和 await 后的代码)最终也大多会被放入事件队列。

三、Event Loop 的工作流程

在Dart中,任务分为两种:事件任务(Event Task)和微任务(Microtask)。

Dart的事件循环会先执行所有微任务队列中的任务,直到微任务队列为空,然后才会从事件任务队列中取出一个任务执行,之后再次检查微任务队列,如此循环。

1. 微任务(Microtask)

  • 微任务通常用于在当前事件循环的末尾、在事件任务开始之前执行一些紧急的工作。

  • 微任务队列比事件任务队列有更高的优先级。

  • 我们可以通过scheduleMicrotask函数来添加一个微任务,也可以使用Future.microtask

  • 微任务包括但不限于:

    • scheduleMicrotask添加的任务。

    • Future对象创建时,通过.then.catchError.whenComplete添加的回调,这些回调会被安排为微任务(但注意,不是所有的Future回调都是微任务,有些情况可能会被安排为事件任务,例如使用Future.delayed)。

(1)特点

优先级高,在当前事件循环的末尾(或下一个事件任务之前)执行,用于需要紧急处理但不阻塞UI的任务。

(2)常见来源

Future 的 thencatchErrorwhenComplete 回调

Future(() => 42).then((value) => print(value)); // 微任务

scheduleMicrotask 函数

scheduleMicrotask(() => print('Microtask'));

Future.microtask

Future.microtask(() => print('Microtask'));

2. 事件任务(Event Task)

  • 事件任务包括I/O、计时器、绘制事件、用户输入事件等。

  • 常见的事件任务有:

    • Timer(Future.delayed内部也是使用Timer)创建的任务。

    • I/O操作完成后的回调。

    • UI绘制事件(在Flutter中)。

    • 用户输入事件(如点击、滑动等)。

(1)特点

优先级较低,在微任务队列清空后执行,代表异步事件(如I/O、计时器、用户交互)。

(2)常见来源

Future 构造函数

Future(() => print('Event Task'));

计时器(Timer

Timer(Duration(seconds: 1), () => print('Timer Event'));

I/O 操作(如文件读写、网络请求)

File('path').readAsString().then((content) => print(content));

用户交互事件(在Flutter中)

GestureDetector(onTap: () => print('Tap Event'));

Stream 的监听回调(如 Stream.listen

事件循环的运行遵循一个非常严格的顺序,可以用以下伪代码表示:

Dart 事件循环的优先级:同步代码 > 微任务队列 > 事件任务队列

void eventLoop() {while (true) { // 循环永不停止// 1. 首先,处理所有微任务while (microtaskQueue.isNotEmpty) {final microtask = microtaskQueue.removeFirst();execute(microtask);}// 2. 微任务队列清空后,处理事件队列中的*一个*事件if (eventQueue.isNotEmpty) {final event = eventQueue.removeFirst();execute(event);}// 3. 回到步骤1,检查微任务队列(可能在执行事件时又产生了新的微任务)}
}

关键点:

  • 一个事件循环周期(Turn)只处理事件队列中的一个事件

  • 但在处理这一个事件之前,必须确保微任务队列是完全空的

  • 执行事件的过程中,可能会产生新的微任务和事件,它们会被加入到相应的队列中。

示例代码:

void main() {// 微任务scheduleMicrotask(() => print('Microtask 1'));// 事件任务Future(() => print('Event Task 1'));// 另一个微任务Future.microtask(() => print('Microtask 2'));// 另一个事件任务Future.delayed(Duration(seconds: 1), () => print('Event Task 2'));print('Main');
}

输出顺序:

  • 首先打印 'Main',因为它是同步代码。

  • 然后执行微任务队列:先打印 'Microtask 1',然后打印 'Microtask 2'。

  • 接着执行事件任务队列:打印 'Event Task 1'。

  • 最后,在延迟1秒后打印 'Event Task 2'。

注意:虽然Future.delayed是一个Future,但它内部使用Timer(事件任务)来延迟执行,所以它的回调是事件任务。

总结:

  • 微任务:由scheduleMicrotaskFuture.microtask以及一些Future的回调(如.then)添加的任务。

  • 事件任务:Timer、I/O、UI事件、用户输入事件等。

四、结合 Future 和 async/await 理解

Future 并不是并行执行的,它只是一个承诺在未来某个时间点返回结果的对象。它的工作流程完美体现了事件循环的机制。

让我们分解一个 Future 的执行过程:

void main() {print('A'); // 同步代码// 创建一个 FutureFuture(() {print('B'); // 异步任务return 100;}).then((value) {// then 回调print('C: $value');});print('D'); // 同步代码
}

执行顺序分析:

  1. main() 函数开始执行。

  2. print('A') 是同步代码,立即执行。输出:A

  3. 遇到 Future(() { print('B'); ... })

    • Future 的构造函数会立即执行,它将传入的匿名函数 () { print('B'); ... } 作为一个任务添加到事件队列(Event Queue)中

    • Future 对象本身被立即返回。

  4. 调用 .then(...)。它注册了一个回调函数((value) { print('C'); }),这个回调不会立即执行,而是被存储起来,等待 Future 完成后触发。

  5. print('D') 是同步代码,立即执行。输出:D

  6. 此时,main() 函数这个同步代码执行完毕。事件循环开始工作。

  7. 事件循环检查微任务队列(Microtask Queue),发现为空。

  8. 事件循环从事件队列(Event Queue)中取出第一个任务,即我们刚才加入的匿名函数 () { print('B'); ... },并执行它。

    • 执行 print('B'),输出:B

    • 函数返回 100,标志着这个 Future 完成了。

  9. Future 完成的关键步骤:当 Future 完成后,它的 then 回调函数不会直接被调用,而是被包装成一个微任务(Microtask),加入到微任务队列的末尾!

  10. 当前的事件任务(打印B)处理完了。按照规则,事件循环不会立刻去取下一个事件,而是再次检查微任务队列

  11. 它发现微任务队列中有一个新任务(执行 then 回调),于是取出并执行它。

    • 执行 print('C: 100'),输出:C: 100

  12. 微任务队列再次清空。事件循环这才继续从事件队列中取下一个事件(如果有的话)。

最终输出顺序是:A -> D -> B -> C: 100

async/await 的本质

async/await 只是 Future 和 then 的语法糖,它们底层完全依赖相同的事件循环机制。

  • async 标记的函数会隐式返回一个 Future

  • await 关键字会将其后的表达式封装成一个 Future,然后暂停当前 async 函数的执行

  • 这个“暂停”并不是阻塞线程,而是立即将线程的控制权交还给事件循环,让它去处理其他任务。

  • 当 await 后的 Future 完成后,其后的代码会被包装成一个微任务,加入到微任务队列中,等待执行。

所以,从事件循环的视角看,await 之后的代码和 .then() 里的回调没有区别,都是微任务。

五、重要结论与启示

  1. 永不阻塞:Dart 线程永远不会被阻塞,它只是在等待事件时变得空闲。真正的 I/O 等工作由操作系统完成,完成后通过事件通知 Dart。

  2. 微任务优先:微任务队列的优先级远高于事件队列。滥用微任务(例如使用 scheduleMicrotask 执行长时间操作)会“饿死”事件队列,导致UI无法更新、手势无法响应。

  3. 执行顺序的可预测性:只要你理解了事件循环的流程,就能准确预测异步代码的执行顺序。同步代码总是最先执行,然后是微任务,最后才是事件。

  4. 不要阻塞事件循环:永远不要在事件循环的任务中执行非常耗时的同步代码(如复杂的数学计算、无限循环)。因为这会使事件循环卡在这个任务上,无法处理队列中的其他任务(微任务和事件),导致应用冻结。对于这种CPU密集型任务,必须使用 Isolate

六、面试题

Q:请写出代码执行结果。

void main() {scheduleMicrotask(() => print('A'));Future(() => print('B')).then(() {print('C');return Future(() => print('D'));}).then(() => print('E'));Future.microtask(() => print('F'));print('G');}

总结输出顺序:

  • 同步代码: G

  • 微任务: A, F

  • 事件任务: B

  • 微任务: C (来自第一个 then 回调)

  • 事件任务: D

  • 微任务: E (来自第二个 then 回调)

所以输出顺序是: G, A, F, B, C, D, E


文章转载自:

http://XYe5ghnF.wrkcw.cn
http://WqIDFGXp.wrkcw.cn
http://ZiuLPUuq.wrkcw.cn
http://GEljjtnR.wrkcw.cn
http://ApsvYdiB.wrkcw.cn
http://oNU86BLq.wrkcw.cn
http://jbu9PfHn.wrkcw.cn
http://yl5k1Dh9.wrkcw.cn
http://jj4s2B5I.wrkcw.cn
http://Wul6ORwg.wrkcw.cn
http://F0h6PYhw.wrkcw.cn
http://1FjihhiS.wrkcw.cn
http://WHQhgOg8.wrkcw.cn
http://JGWjlvyi.wrkcw.cn
http://Tjxc4D2n.wrkcw.cn
http://bF7aU3Tl.wrkcw.cn
http://jRHMzdyG.wrkcw.cn
http://s7ZeczoZ.wrkcw.cn
http://NhJ99UV8.wrkcw.cn
http://bGjIP4Ut.wrkcw.cn
http://MGw26RKX.wrkcw.cn
http://B8tqBYoS.wrkcw.cn
http://ijZxgRPK.wrkcw.cn
http://L9lV4mmx.wrkcw.cn
http://wkCEnsau.wrkcw.cn
http://UvGZHTs6.wrkcw.cn
http://LoCFrA7c.wrkcw.cn
http://1MLKwkvd.wrkcw.cn
http://UqnMwgvU.wrkcw.cn
http://otLZhESl.wrkcw.cn
http://www.dtcms.com/a/376565.html

相关文章:

  • C++/Java编程小论——方法设计与接口原则总结
  • Java-Spring入门指南(四)深入IOC本质与依赖注入(DI)实战
  • 线扫相机采集图像起始位置不正确原因总结
  • JVM 对象创建的核心流程!
  • 秋日私语:一片落叶,一个智能的温暖陪伴
  • springCloud之配置/注册中心及服务发现Nacos
  • 第1讲 机器学习(ML)教程
  • Ubuntu 系统 YOLOv8 部署教程(GPU CPU 一键安装)
  • 【C++】string 的使用(初步会用 string,看这一篇文章就够了)
  • 基于 lua_shared_dict 的本地内存限流实现
  • 基于场景的自动驾驶汽车技术安全需求制定方法
  • 【lucene】pointDimensionCount` vs `pointIndexDimensionCount`:
  • 大语言模型入门指南:从原理到实践应用
  • 旧设备新智慧:耐达讯自动化RS232转Profibus连接流量泵工业4.0通关秘籍
  • 扭蛋机小程序有哪些好玩的创新功能?
  • 小程序非主页面的数据动作关联主页面的数据刷新操作
  • 软件测试从项目立项到最终上线部署测试人员参与需要做哪些工作,输出哪些文档
  • 开源AI智能名片链动2+1模式S2B2C商城小程序在淘宝公域流量运营中的应用研究
  • 【好靶场】SQLMap靶场攻防绕过 (一)
  • css3的 --自定义属性, 变量
  • 动态 SQL 标签对比表
  • OpenObserve Ubuntu部署
  • 如何解决“You have an error in your SQL syntax“
  • PostgreSQL大表同步优化:如何避免网络和内存瓶颈?
  • vue3 的痛点
  • 在 Ubuntu 22.04 系统(CUDA 12.9)中,通过本地DEB 包安装 cuDNN 9.13.0 的方法步骤
  • MySQL整理【03】事务隔离级别和MVCC
  • 信息检索2
  • Unity2019用vscode的问题
  • iOS 文件管理与能耗调试结合实战 如何查看缓存文件、优化电池消耗、分析App使用记录(uni-app开发与性能优化必备指南)