一、异步编程为什么让人又爱又恨
Dart语言中的异步编程就像外卖小哥送餐,你点了单(发起请求)之后不用干等着,可以继续刷剧(执行其他代码)。等餐到了(响应返回)自然会通知你。这种机制让程序不会卡死,但处理不好也容易翻车。
举个例子,我们经常用Future处理网络请求:
// 示例1:基本的Future使用(技术栈:Dart/Flutter)
Future<void> fetchUserData() async {
print('开始获取用户数据...'); // 同步代码立即执行
// 模拟网络请求延迟
final data = await Future.delayed(
Duration(seconds: 2),
() => {'name': '老王', 'age': 30},
);
print('获取到数据: $data'); // 异步代码后执行
}
void main() {
fetchUserData();
print('我正在做其他事情...'); // 不会等待网络请求
}
这个简单的例子展示了异步的核心优势:非阻塞。但现实往往更复杂,比如多个异步任务嵌套时:
// 示例2:嵌套的异步陷阱(技术栈:Dart)
Future<void> buyCoffee() async {
await login(); // 等待登录
await selectCoffee(); // 等待选择
await pay(); // 等待支付
// 这里已经形成了"回调地狱"的雏形
}
二、那些年我们踩过的异步坑
1. 未处理的异常就像定时炸弹
// 示例3:未捕获的异步异常(技术栈:Dart)
Future<void> riskyOperation() async {
await Future.delayed(Duration(seconds: 1));
throw Exception('意外爆炸!');
}
void main() {
riskyOperation(); // 没有处理异常
print('程序继续运行...'); // 异常会被静默吞掉
}
正确的处理方式应该是:
// 示例4:正确的异常处理(技术栈:Dart)
void main() async {
try {
await riskyOperation();
} catch (e) {
print('捕获到异常: $e');
}
}
2. 并行变串行的性能陷阱
// 示例5:错误的串行执行(技术栈:Dart)
Future<void> fetchAllData() async {
final user = await fetchUser(); // 等待用户数据
final posts = await fetchPosts(); // 然后才请求帖子
// 两个本可以并行的请求变成了串行
}
应该使用Future.wait实现并行:
// 示例6:正确的并行处理(技术栈:Dart)
Future<void> fetchAllData() async {
final results = await Future.wait([
fetchUser(),
fetchPosts(),
]);
// 同时发起两个请求
}
三、Stream处理的常见误区
Dart中的Stream就像传送带,数据会源源不断过来。新手常犯的错误是忘记关闭:
// 示例7:Stream泄漏问题(技术栈:Dart)
Stream<int> countNumbers() async* {
var i = 0;
while (true) {
yield i++;
await Future.delayed(Duration(seconds: 1));
}
}
void main() {
final subscription = countNumbers().listen((num) {
print(num);
if (num == 5) {
// 忘记调用 subscription.cancel();
}
});
// Stream会持续占用资源
}
正确的做法是:
// 示例8:正确的Stream处理(技术栈:Dart)
void main() async {
final subscription = countNumbers().listen((num) {
print(num);
if (num == 5) {
subscription.cancel(); // 及时取消订阅
}
});
}
四、实战中的最佳实践
1. 超时机制必不可少
// 示例9:添加超时控制(技术栈:Dart)
Future<void> fetchWithTimeout() async {
try {
final data = await fetchUserData()
.timeout(Duration(seconds: 3));
print(data);
} on TimeoutException {
print('请求超时');
}
}
2. 使用async/await替代then
// 示例10:两种风格对比(技术栈:Dart)
// 旧式then写法
fetchUser().then((user) {
return fetchPosts(user.id);
}).then((posts) {
print(posts);
});
// 更清晰的async/await写法
void main() async {
final user = await fetchUser();
final posts = await fetchPosts(user.id);
print(posts);
}
3. 重要数据需要错误重试
// 示例11:带重试机制的请求(技术栈:Dart)
Future<T> retry<T>(Future<T> Function() fn,
{int maxAttempts = 3}) async {
for (var i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (e) {
if (i == maxAttempts - 1) rethrow;
await Future.delayed(Duration(seconds: 1));
}
}
throw StateError('不可能执行到这里');
}
五、Isolate不是银弹
虽然Isolate可以实现真正的并行,但通信成本很高:
// 示例12:Isolate的通信开销(技术栈:Dart)
void isolateFunction(SendPort sendPort) {
var counter = 0;
Timer.periodic(Duration(seconds: 1), (_) {
sendPort.send(counter++); // 每次通信都有序列化开销
});
}
void main() async {
final receivePort = ReceivePort();
await Isolate.spawn(isolateFunction, receivePort.sendPort);
receivePort.listen((message) {
print('收到: $message');
});
}
六、总结与建议
- 始终处理异步异常,不要静默失败
- 能用async/await就别用then回调
- 并行任务优先考虑Future.wait
- Stream使用后记得取消订阅
- 关键操作添加超时和重试机制
- Isolate适合CPU密集型任务,但要注意通信成本
记住,异步编程就像烹饪,火候(时机)掌握不好,再好的食材(代码)也会煮糊。多练习这些模式,你就能像大厨一样游刃有余。
评论