Dart 异步编程:Future、async/await 与 Stream

FreeGuideOnline 最新 2026-06-17

Dart 异步编程:Future、async/await 与 Stream

Dart 是一种单线程语言,通过事件循环实现异步操作。掌握 Futureasync/awaitStream,能让你写出不阻塞 UI 且高效处理数据流的应用程序。本教程面向初学者,将用清晰的代码示例带你理解 Dart 异步编程的核心概念与实战用法。

1. 为什么需要异步?

Dart 程序在运行时只有一个主线程(isolate),如果进行耗时操作(如网络请求、文件读写、定时等待)会阻塞整个线程,导致界面卡死。异步编程允许你在等待操作完成的同时,继续执行其他任务,结果返回后再按顺序处理。

Dart 将耗时工作包装为 FutureStream,由事件循环调度。理解这一点后,我们进入核心知识。

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:更直观的异步写法

asyncawait 是 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 并行请求,但注意错误处理要用 catchErrortry-catch
    • 测试 Stream 时,可以使用 Stream.fromIterable() 快速生成假数据。

掌握这些知识后,你就能轻松应对 Dart 中的网络请求、数据库操作、蓝牙通信等真实异步场景。更多高阶内容(如 StreamTransformerRxDart 库)可在官方文档中进一步探索。