Dart 异步编程:Future、async/await 与 Stream
Dart 异步编程:Future、async/await 与 Stream
Dart 是一种单线程语言,通过事件循环实现异步操作。掌握 Future、async/await 和 Stream,能让你写出不阻塞 UI 且高效处理数据流的应用程序。本教程面向初学者,将用清晰的代码示例带你理解 Dart 异步编程的核心概念与实战用法。
1. 为什么需要异步?
Dart 程序在运行时只有一个主线程(isolate),如果进行耗时操作(如网络请求、文件读写、定时等待)会阻塞整个线程,导致界面卡死。异步编程允许你在等待操作完成的同时,继续执行其他任务,结果返回后再按顺序处理。
Dart 将耗时工作包装为 Future 或 Stream,由事件循环调度。理解这一点后,我们进入核心知识。
2. Future:未来的值
Future<T> 代表一个可能还未完成的异步操作,它最终会返回一个类型为 T 的值,或者抛出一个错误。你可以把它看作一个只执行一次的“令牌”。
2.1 创建 Future
- 构造函数:
Future(() => ...)会立即提交一个任务到事件队列。 - Future.value():直接返回一个已完成的值(同步包装)。
- Future.delayed():延迟指定时间后执行任务。
// 1. 普通构造,返回字符串
Future<String> fetchData() {
return Future(() {
// 模拟耗时操作
sleep(Duration(seconds: 2)); // 仅示意,实际应使用非阻塞等待
return '数据加载完成';
});
}
// 2. 立即完成的 Future
Future<int> immediate = Future.value(42);
// 3. 延迟 Future
Future.delayed(Duration(seconds: 1), () => print('1秒后打印'));
2.2 使用 Future:then、catchError、whenComplete
通过 then 注册回调来处理成功结果,catchError 捕获错误,whenComplete 无论成功或失败都会执行(类似 finally)。
fetchData()
.then((value) {
print('成功: $value');
})
.catchError((error) {
print('出错: $error');
})
.whenComplete(() {
print('操作结束');
});
链式调用:每个 then 都会返回一个新的 Future,可以继续处理。
Future<int> getNumber() => Future.value(10);
getNumber()
.then((n) => n * 2)
.then((n) => '结果是 $n')
.then(print); // 输出:结果是 20
2.3 组合多个 Future
- Future.wait:等待所有 Future 完成,返回结果列表。若任一失败,则立即失败。
- Future.any:返回最先完成的 Future 的结果(无论成功或失败)。
Future<String> task1 = Future.delayed(Duration(seconds: 2), () => '任务一');
Future<String> task2 = Future.delayed(Duration(seconds: 1), () => '任务二');
Future.wait([task1, task2]).then((results) {
print(results); // ['任务一', '任务二'],等最慢的任务完成
});
Future.any([task1, task2]).then((result) {
print(result); // '任务二',因为 task2 更快
});
3. async / await:更直观的异步写法
async 和 await 是 Dart 提供的语法糖,让异步代码看起来像同步代码,极大提高可读性。
3.1 声明异步函数
在函数体前加上 async 关键字,该函数自动返回一个 Future。即使函数内没有显式返回 Future,Dart 也会将返回值包装为 Future。
// 返回 Future<String> 的函数
Future<String> loadUser() async {
// 一些异步操作...
return 'Alice';
}
3.2 使用 await 等待结果
await 只能在 async 函数内部使用,它会暂停当前函数的执行,直到 Future 完成,并直接返回结果值。
Future<void> main() async {
print('开始');
String user = await loadUser();
print('用户名: $user'); // 等 loadUser 完成后才会执行
print('结束');
}
// 输出顺序:开始 → (等待) → 用户名: Alice → 结束
对比之前的 then 链,await 让逻辑更线性。
3.3 错误处理:try-catch
在 async 函数中,用 try-catch 捕获异常,完全等同于同步代码的写法。
Future<int> riskyOperation() async {
throw Exception('出错了');
}
Future<void> run() async {
try {
int result = await riskyOperation();
print(result);
} catch (e) {
print('捕获到异常: $e');
}
}
3.4 注意点
- 如果
async函数中不写await,函数也一样返回Future,但内部代码会同步执行直到遇到第一个await。 - 避免滥用
async:如果一个函数没有异步操作,就不要加async,否则会无意义地包装出Future。
4. Stream:异步数据流
当数据需要分多次产出(如文件逐行读取、传感器实时数据、WebSocket 消息)时,Stream 是更好的选择。它相当于一个异步的迭代器,可以不断推送数据。
4.1 两种 Stream 类型
- 单订阅流(Single-subscription):默认类型,只能被一个监听器监听。适合一次性连续的数据序列(如文件内容)。
- 广播流(Broadcast):允许多个监听器同时订阅。适合事件总线等场景。
4.2 创建 Stream
使用 async* 和 yield 生成
通过 async* 标记函数为异步生成器,用 yield 逐个产出数据。
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
使用 StreamController
更灵活的方式,可手动控制数据的加入、错误和关闭。
import 'dart:async';
StreamController<String> controller = StreamController<String>.broadcast();
// 监听
controller.stream.listen((data) {
print('收到: $data');
});
// 添加数据
controller.add('消息1');
controller.add('消息2');
// 关闭流
controller.close();
4.3 监听 Stream
通过 listen 方法注册回调,可分别处理数据、错误和流关闭。
Stream<int> stream = countStream(3);
stream.listen(
(data) => print('数据: $data'),
onError: (err) => print('错误: $err'),
onDone: () => print('流已关闭'),
);
// 输出:
// 数据: 1
// 数据: 2
// 数据: 3
// 流已关闭
4.4 变换 Stream
Stream 提供链式变换方法,类似集合操作,但惰性执行。
map:转换每个数据项。where:过滤数据。expand:将每个元素展开为多个。take/skip:截取/跳过指定数量。
countStream(5)
.where((n) => n.isEven)
.map((n) => '偶数:$n')
.listen(print);
// 输出:偶数:2 偶数:4
4.5 使用 await for 循环
在 async 函数内,可以用 await for 遍历 Stream 的每一个事件,直到流关闭。
Future<void> printStream(Stream<int> stream) async {
await for (final value in stream) {
print('获取到: $value');
}
print('流结束');
}
这种方式适合需要按顺序处理每个数据的场景,比如写入数据库。
4.6 错误处理
在 await for 中可以使用 try-catch 包裹整个循环,或在 listen 中指定 onError。如果使用 StreamController,可以通过 addError 发送错误。
Stream<int> errorStream() async* {
yield 1;
throw Exception('流中异常');
}
// 使用 await for 捕获
try {
await for (final val in errorStream()) {
print(val);
}
} catch (e) {
print('捕获: $e');
}
5. 总结与实践建议
-
选 Future 还是 Stream?
- 单次异步结果 →
Future - 多次异步事件序列 →
Stream
- 单次异步结果 →
-
推荐写法
- 尽可能使用
async/await,代码清晰易维护。 - 长时间监听的 Stream 要记得关闭(
StreamController.close()或取消订阅)。 - 避免在
async函数内做 CPU 密集型计算,应使用Isolate处理。
- 尽可能使用
-
调试技巧
- 使用
Future.wait并行请求,但注意错误处理要用catchError或try-catch。 - 测试 Stream 时,可以使用
Stream.fromIterable()快速生成假数据。
- 使用
掌握这些知识后,你就能轻松应对 Dart 中的网络请求、数据库操作、蓝牙通信等真实异步场景。更多高阶内容(如 StreamTransformer、RxDart 库)可在官方文档中进一步探索。