Dart 异步编程:Future 与 async/await
Dart 采用单线程运行机制,所有代码在同一时间只能执行一项任务。遇到网络请求、文件读写等耗时操作时,若直接原地等待,会导致主线程阻塞、界面卡顿。引入异步编程,能让主线程在等待耗时操作完成期间,继续处理其他逻辑,从而保证程序流畅运行。 本指南将手把手教你使用 Future 与 async/await 高效编写 Dart 异步代码。
阶段一:理解异步执行的基本流程
在编写代码前,明确 Dart 处理异步任务的标准路径。异步任务不会立即返回结果,而是返回一个“占位符”对象,待后台任务完成后,再将真实数据填充进去。
- 创建
Future实例。该实例代表一个将在未来某个时刻完成的操作。 - 注册 回调函数。通过指定成功或失败时的处理逻辑,确保数据返回后程序能正确响应。
- 监听 状态变化。Dart 事件循环会在后台任务结束后,自动触发对应的回调逻辑。
| 异步对象类型 | 适用场景说明 | 返回值特征 |
|---|---|---|
Future<T> |
任务有确定返回值 | 返回具体泛型数据类型 T |
Future<void> |
仅需确认任务完成状态 | 无实际返回值 |
Future.error |
明确标识异步任务将失败 | 直接抛出异常对象 |
阶段二:编写并处理原生 Future
使用 Dart 原生提供的工具函数生成 Future,并学会使用 .then() 链式调用获取结果。
- 导入 异步操作基础库。在文件顶部添加
import 'dart:async';确保基础类型可用(现代 Dart 版本已内置,但显式声明可增强代码可读性)。 - 包装 耗时逻辑。使用
Future.delayed函数模拟需要等待的操作。该函数接收两个参数:第一个是Duration类型的时间间隔,第二个是闭包函数(即具体要执行的任务)。 - 链接 处理逻辑。在
Future对象后调用.then()方法,传入接收返回值的函数。 - 打印 验证结果。在终端运行代码,观察 控制台输出顺序,确认耗时操作未阻塞后续同步代码的执行。
// 模拟耗时操作:2秒后返回字符串结果
void main() {
print('任务开始');
Future.delayed(Duration(seconds: 2), () {
return '网络请求成功:数据已获取';
}).then((result) {
print('回调执行:$result');
});
print('主线程继续运行');
}
// 预期输出顺序:任务开始 -> 主线程继续运行 -> (等待2秒) -> 回调执行:网络请求成功:数据已获取
```
5. **捕获** 链式异常。在 `.then()` 后追加 `.catchError()` 方法,传入错误处理函数,防止未处理的异常导致程序崩溃。
---
### 阶段三:使用 async/await 重构代码
`.then()` 链式调用在逻辑复杂时会形成多层嵌套。使用 `async/await` 语法糖,可将异步代码写得像普通同步代码一样直观。
1. **标记** 异步函数。在函数返回类型前添加 `Future` 关键字,并在函数名与参数列表之间添加 `async` 关键字。此操作告知 Dart 编译器该函数内部包含异步逻辑。
2. **阻塞等待** 结果。在可能耗时的 `Future` 表达式前添加 `await` 关键字。程序执行到此处时会暂停该函数内部的后续代码,直到 `Future` 完成并返回结果,再将结果赋值给左侧变量。
3. **包裹** 异常逻辑。将包含 `await` 的代码块放入标准的 `try { ... } catch (e) { ... }` 结构中,直接使用 `catch` 捕获同步风格的异常。
```dart
Future<String> fetchNetworkData() async {
try {
// 等待模拟网络请求,耗时1秒
String data = await Future.delayed(Duration(seconds: 1), () => '用户信息');
print('数据加载完成:$data');
return data;
} catch (e) {
print('请求失败:$e');
throw Exception('数据获取异常');
}
}
Future<void> processData() async {
print('开始处理');
// 等待 fetchNetworkData 完成并获取结果,代码在此暂停
String result = await fetchNetworkData();
// 只有上方 await 完成后,才会执行下方代码
print('处理完毕,使用结果:$result');
}
- 验证 执行流。运行
processData(),确认print('开始处理')执行后,程序等待 1 秒,随后打印加载完成信息,最后打印处理完毕信息。整个流程符合自上而下的阅读直觉。
阶段四:配置超时与降级机制
网络请求或外部服务可能永久挂起。设置 明确的超时阈值,防止 await 无限期阻塞程序。
- 引入 超时控制。直接在目标
Future表达式后调用.timeout()方法。 - 定义 时间阈值。向
.timeout()传入Duration对象作为第一个参数,设定允许等待的最大时间。 - 指定 超时行为。通过
onTimeout参数传入一个回调函数。该函数必须返回一个与原Future类型一致的值,或抛出特定异常。
import 'dart:async';
Future<String> fetchWithTimeout() async {
try {
// 设置最大等待时间为 500 毫秒
String res = await Future.delayed(Duration(seconds: 2)).timeout(
Duration(milliseconds: 500),
onTimeout: () {
throw TimeoutException('接口响应超时');
},
);
return res;
} catch (e) {
return '降级数据:使用本地缓存';
}
}
- 调整 降级策略。根据业务需求修改
onTimeout的返回值。若业务强依赖实时数据,抛出 异常由上层统一处理;若允许数据延迟,返回 静态兜底数据保证界面正常渲染。
阶段五:并行执行独立异步任务
多个相互独立的异步操作若使用多个 await 串行执行,总耗时会线性累加。改用 并行执行策略,显著缩短整体等待时间。
- 收集 任务列表。将所有独立的异步
Future对象存入一个List集合中,确保它们处于已触发但未等待的状态。 - 调用 等待方法。使用
Future.wait()静态方法包裹任务列表。该方法会返回一个新的Future,当列表中所有任务全部完成时,该新Future才会结束。 - 提取 组合结果。
Future.wait()返回的结果是一个列表,顺序与传入任务的顺序严格一致。使用索引或解构语法快速读取各任务返回值。
Future<void> runParallelTasks() async {
// 1. 立即触发所有任务(注意:此处声明时不加 await)
Future<String> task1 = Future.delayed(Duration(seconds: 2), () => '数据库连接');
Future<String> task2 = Future.delayed(Duration(seconds: 1), () => '配置文件加载');
Future<String> task3 = Future.delayed(Duration(seconds: 1), () => '日志初始化');
// 2. 统一等待,总耗时取决于最慢的任务(约2秒,而非4秒)
List<String> results = await Future.wait([task1, task2, task3]);
print('所有任务完成');
print(results[0]); // 输出:数据库连接
print(results[1]); // 输出:配置文件加载
print(results[2]); // 输出:日志初始化
}
-
处理 单点失败。若需允许部分任务失败而不中断整体流程,在
Future.wait()中设置eagerError: false参数。使用try-catch捕获错误,遍历结果列表时判断 每一项的状态,将失败项替换为默认值。 -
对比 性能差异。分别运行串行(多次独立
await)与并行(Future.wait)代码,记录 控制台总耗时差值。并行方案将多个任务的耗时从相加变为取最大值,CPU 时间片利用率大幅提升。 -
应用 组合策略。在实际业务中,将核心流程拆分为无依赖并行阶段与有依赖串行阶段。先使用
Future.wait批量加载基础资源,再利用await按顺序处理资源关联逻辑。 -
封装 通用工具类。将常用的异步模板抽取为独立函数,统一在返回值前添加
async,在调用处统一使用await获取结果。确保每个异步函数只负责单一职责,返回 明确的Future<T>类型。

暂无评论,快来抢沙发吧!