详细解释Flutter中 async/await 的工作原理

320 阅读4分钟

在 Flutter(Dart)中,async/await异步编程 的核心机制。Dart 使用 单线程的事件循环 进行任务调度,而 async/await 提供了一种更直观的方式来管理异步任务。

本篇文章将深入解析 async/await执行原理,包括:

  1. Dart 的异步模型
  2. Future 的基本概念
  3. async/await 的底层实现
  4. 事件循环(Event Loop)与任务调度
  5. 微任务(Microtask)与事件队列(Event Queue)
  6. await 背后的状态机
  7. async/await 源码分析
  8. 最佳实践与常见问题

1. Dart 的异步模型

Flutter 的 UI 渲染依赖于 主线程(UI 线程) ,如果任务执行时间过长,会导致界面卡顿(掉帧)。为了保证流畅性,Flutter 采用 异步模型 处理耗时任务,比如:

  • 网络请求
  • 文件读写
  • 数据库操作
  • 动画
  • I/O 操作

Dart 的单线程模型

Dart 采用 单线程事件循环,与 JavaScript 类似:

  • 事件按照 先进先出(FIFO) 执行

  • 任务分为:

    • 微任务(Microtask) :优先执行(类似 JS 的 Promise.then
    • 事件任务(Event Task) :比如 I/O、鼠标点击、定时器等

2. Future 的基本概念

在 Dart 中,Future 代表 一个异步操作的结果

dart
复制编辑
Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () => "数据加载完成");
}

使用 .then() 处理:

dart
复制编辑
fetchData().then((data) {
  print(data);
});

.then() 方式 嵌套太多,会变得难以阅读(回调地狱):

dart
复制编辑
fetchData().then((data) {
  process(data).then((result) {
    save(result).then((_) {
      print("完成");
    });
  });
});

为了解决这个问题,Dart 引入了 async/await


3. async/await 的工作原理

3.1. 使用 async/await 让代码更直观

dart
复制编辑
Future<void> loadData() async {
  String data = await fetchData();
  print(data);
}

等价于:

dart
复制编辑
fetchData().then((data) {
  print(data);
});

async/await 只是 Future 的语法糖,它不会改变 Future 本身的行为,而是让异步代码像同步代码一样编写。


4. 事件循环(Event Loop)与任务调度

Flutter 运行时采用 事件循环(Event Loop) 处理异步任务:

  1. 同步任务 立即执行
  2. 微任务(Microtask) 在当前事件循环执行完毕后立即执行
  3. 事件任务(Event Task) 交给事件队列,等待空闲时执行

Dart 采用 单线程事件循环 处理所有任务,执行顺序

  • 先执行 同步任务
  • 再执行 所有微任务 (特点有:优先级高,比事件任务先执行;一次性执行完所有微任务;不能主动终止;适用于 短小的任务 任务调度(State 更新)(日志收集)(UI 细节调整))
  • 最后执行 一个事件任务(特点有:事件任务 适用于 IO、定时器、用户交互等任务,优先级低于微任务,如用户输入(点击、键盘事件))
  • 然后继续执行微任务(如果有)

示例:

dart
复制编辑
void main() {
  print("1");
  Future(() => print("2"));
  scheduleMicrotask(() => print("3"));
  print("4");
}

执行顺序:

  1. print("1")
  2. print("4")(同步任务执行完毕)
  3. print("3")(微任务队列执行)
  4. print("2")(事件任务执行)

5. await 背后的状态机

await 并不会阻塞线程,而是将代码拆分为多个 Future 回调,然后利用 状态机 进行调度。

示例

dart
复制编辑
Future<void> main() async {
  print("A");
  await Future.delayed(Duration(seconds: 1));
  print("B");
}

等价代码

dart
复制编辑
Future<void> main() {
  print("A");
  return Future.delayed(Duration(seconds: 1)).then((_) {
    print("B");
  });
}

执行过程

  1. print("A")
  2. Future.delayed 被加入 事件队列
  3. 主线程任务执行完毕,事件循环开始处理 Future
  4. print("B")

await 关键字 会拆分成多个 Future 回调,每个 await 相当于创建了一个新的 Future.then


6. async/await 源码解析

在 Dart 源码中,async 代码会被编译成 Future 回调链,并使用 状态机 控制执行。

查看 dart:async 中的 Future 代码:

dart
复制编辑
Future<T> async<T>(FutureOr<T> Function() computation) {
  return Future<T>(() {
    return computation();
  });
}

Dart 运行时会自动将 async 方法转换成 Future 状态机。

示例

dart
复制编辑
Future<int> compute() async {
  int result = await Future.value(10);
  return result;
}

实际执行时,相当于:

dart
复制编辑
Future<int> compute() {
  return Future.value(10).then((result) {
    return result;
  });
}

Dart 运行时 通过状态机调度 await 关键字,使其执行时不会阻塞主线程


7. async/await 最佳实践

7.1. 避免 await 阻塞 UI

错误示例:

dart
复制编辑
void main() async {
  await Future.delayed(Duration(seconds: 3)); // UI 会卡顿
  runApp(MyApp());
}

正确方式:

dart
复制编辑
void main() {
  runApp(MyApp());
  Future.delayed(Duration(seconds: 3), () {
    print("初始化完成");
  });
}

7.2. 处理异常

使用 try-catch 捕获 async/await 异常:

dart
复制编辑
Future<void> fetchData() async {
  try {
    String data = await Future.error("网络异常");
    print(data);
  } catch (e) {
    print("捕获异常: $e");
  }
}

避免未处理的异常导致应用崩溃


7.3. 并行执行多个 await

错误示例:

dart
复制编辑
Future<void> loadData() async {
  String data1 = await fetchData1();
  String data2 = await fetchData2();
}

优化:

dart
复制编辑
Future<void> loadData() async {
  var future1 = fetchData1();
  var future2 = fetchData2();
  String data1 = await future1;
  String data2 = await future2;
}

这样 fetchData1()fetchData2() 并行执行,提高性能


8. 结论

  • async/await 只是 Future 的语法糖,本质是 状态机 + Future.then()
  • await 不会阻塞 UI 线程
  • Dart 采用 事件循环 处理异步任务
  • 微任务(Microtask)优先于事件任务(Event Task)
  • 优化:并行 await,避免 UI 阻塞

理解 async/await 的底层原理,有助于编写更高效、可维护的 Flutter 代码