Dart Stream 流式编程深度解析:从数据管道到响应式开发
一句话概括
Dart 的 Stream 是一个可订阅的异步事件序列抽象,它提供了从单次事件(Future)到持续数据流(Stream)的编程范式跃迁,通过 StreamController、Transformers、Subscription 等组件构成了一套完整的流式数据处理管道。
背景与意义
在很多业务场景中,数据不是一次到位而是分批到达的:WebSocket 推送、文件逐行读取、蓝牙设备数据包、用户连续滚动加载、MQTT 消息订阅……这些场景如果用 Future 加回调的方式来处理,代码很快就会陷入嵌套回调地狱和混乱的状态管理。
Stream 正是为解决这类场景而生的。在 Flutter 生态中,Stream 不仅仅是异步编程的基础设施,更是状态管理的底层通道——Bloc 模式的核心就是 Stream,Firebase 实时数据库的每一次数据变更也通过 Stream 推送。可以说,无论你是做 Flutter 业务开发还是写 Dart 底层工具库,Stream 都是无法绕过的核心概念。
概念与定义
Stream
一个异步事件的序列。与 Future<T> 的”一次性”不同,Stream<T> 可以在生命周期内发射零个、一个或多个事件。
StreamController
手动创建和管理 Stream 的总控节点。你可以通过它添加事件、监听状态、控制背压。
StreamSubscription
调用 stream.listen() 时返回的对象。用于控制监听的生命周期——暂停、恢复、取消。
StreamTransformer
对 Stream 进行数据变换的组件,可以复用变换逻辑。
同步 vs 异步 Stream
- 异步 Stream:事件在监听后异步到达(默认行为)
- 同步 Stream:事件在当前微任务中同步触发,用于特定优化场景
最小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'dart:async';
void main() {
// 创建一个 Stream
final stream = Stream<int>.periodic(
Duration(seconds: 1),
(count) => count,
).take(5); // 只取5个事件
// 订阅 Stream
final subscription = stream.listen(
(data) => print('收到: $data'),
onError: (error) => print('错误: $error'),
onDone: () => print('流已关闭'),
cancelOnError: false,
);
// 5秒后取消订阅
Future.delayed(Duration(seconds: 3), () {
subscription.cancel();
print('已取消订阅');
});
}
输出:
1
2
3
4
收到: 0
收到: 1
收到: 2
已取消订阅 ← 注意:流在取消后不再接收后续事件
核心知识点拆解
1. Stream 的种类
graph TD
A[Stream 分类] --> B[单订阅 Single-Subscription]
A --> C[广播 Broadcast]
B --> D[只能有一个 listener]
B --> E[类比:视频流-只能一个播放器]
B --> F[通常用于文件 I/O / 网络]
C --> G[可以有多个 listener]
C --> H[类比:电台广播-多人收听]
C --> I[通常用于 UI 事件/状态]
1
2
3
4
5
6
7
8
9
10
11
// 单订阅 Stream
final singleStream = Stream<int>.fromIterable([1, 2, 3]);
singleStream.listen(print); // OK
singleStream.listen(print); // ❌ 抛出 StateError
// 广播 Stream
final controller = StreamController<int>.broadcast();
controller.stream.listen((v) => print('A: $v'));
controller.stream.listen((v) => print('B: $v'));
controller.add(42); // 两个监听器都会收到
controller.close();
2. StreamController 的完整生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class StreamControllerWrapper {
late final StreamController<String> _controller;
StreamControllerWrapper() {
_controller = StreamController<String>(
onListen: () => print('第一个监听器加入'),
onPause: () => print('监听器暂停'),
onResume: () => print('监听器恢复'),
onCancel: () => print('监听器取消'),
);
}
Stream<String> get stream => _controller.stream;
void addData(String data) => _controller.add(data);
void addError(Object error) => _controller.addError(error);
void close() {
_controller.close();
print('StreamController 已关闭');
}
}
3. 核心操作符
Stream 的转换能力是其强大之处:
1
2
3
4
5
6
7
8
9
Stream.fromIterable([1, 2, 3, 4, 5])
.where((n) => n.isEven) // 过滤:仅偶数 → [2, 4]
.map((n) => '数字 $n') // 映射:转换类型
.take(1) // 只取第一个
.skip(1) // 跳过前 N 个
.distinct() // 去重(连续重复)
.expand((s) => s.split('')) // 扁平化
.asyncMap((s) => Future.value(s)) // 异步映射
.transform(StreamTransformer(...)) // 自定义变换
4. async* 生成器与 await for
Dart 提供了语法糖来消费和生成 Stream:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用 async* 生成 Stream
Stream<int> countDown(int from) async* {
for (int i = from; i >= 0; i--) {
yield i; // 每次 yield 发射一个事件
await Future.delayed(Duration(seconds: 1));
}
}
// 使用 await for 消费 Stream
Future<void> processNumbers() async {
await for (final number in countDown(5)) {
print('倒计时: $number');
if (number == 0) print('🚀 发射!');
}
}
await for 本质上是一个无限循环(直到 Stream done 或 break),每次 await 挂起直到下一个事件到达。
5. Stream 的背压处理
当 Stream 生产数据的速度快于消费速度时,需要进行背压处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式 1:使用 buffer 暂存
stream.buffer(Duration(milliseconds: 100)).listen((batch) {
print('批量处理: $batch');
});
// 方式 2:使用 concurrency 控制
stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) async {
await processSlow(data); // 处理完当前数据才继续
sink.add(data);
},
),
);
实战案例
案例 1:文件实时监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import 'dart:io';
class LogTailer {
final String filePath;
RandomAccessFile? _file;
int _position = 0;
LogTailer({required this.filePath});
Stream<String> tail() async* {
_file = await File(filePath).open(mode: FileMode.read);
while (true) {
await Future.delayed(Duration(milliseconds: 500));
await _file!.setPosition(_position);
final bytes = await _file!.read();
if (bytes.isNotEmpty) {
_position += bytes.length;
yield utf8.decode(bytes);
}
}
}
void dispose() {
_file?.closeSync();
}
}
// 使用
void main() async {
final tailer = LogTailer(filePath: '/tmp/app.log');
await for (final line in tailer.tail()) {
if (line.contains('ERROR')) {
print('⚠️ 发现错误: $line');
}
}
}
案例 2:输入去抖(Debounce)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Stream<String> debounceSearch(Stream<String> input, Duration delay) async* {
Timer? timer;
String? latest;
await for (final query in input) {
latest = query;
timer?.cancel();
timer = Timer(delay, () {
// 这个空的 timer 回调不会 yield,用于触发取消
});
// 等待 delay 时间,期间没有新输入则 yield
await Future.delayed(delay);
if (query == latest) {
yield query;
}
}
}
// Flutter 中使用
// TextField → onChanged → StreamController → debounce → 网络请求
final controller = StreamController<String>();
final results = debounceSearch(controller.stream, Duration(milliseconds: 300));
results.listen((query) {
api.search(query); // 只有停顿 300ms 后才搜索
});
案例 3:WebSocket 实时通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class RealtimeClient {
WebSocketChannel? _channel;
final StreamController<ServerEvent> _eventController =
StreamController<ServerEvent>.broadcast();
Stream<ServerEvent> get events => _eventController.stream;
Future<void> connect(String url) async {
_channel = WebSocketChannel.connect(Uri.parse(url));
_channel!.stream.listen(
(message) {
final event = ServerEvent.fromJson(jsonDecode(message));
_eventController.add(event);
},
onError: (error) => _eventController.addError(error),
onDone: () async {
print('连接断开,3秒后重连...');
await Future.delayed(Duration(seconds: 3));
await connect(url);
},
);
}
void send(ClientEvent event) {
_channel?.sink.add(jsonEncode(event.toJson()));
}
void dispose() {
_channel?.sink.close();
_eventController.close();
}
}
enum ServerEventType { message, typing, presence }
class ServerEvent {
final ServerEventType type;
final Map<String, dynamic> payload;
ServerEvent({required this.type, required this.payload});
factory ServerEvent.fromJson(Map<String, dynamic> json) {
return ServerEvent(
type: ServerEventType.values.byName(json['type']),
payload: json['payload'],
);
}
}
案例 4:Bloc 模式中的 Stream 协程
Flutter Bloc 通过 Stream 实现状态管理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Bloc 内部本质上是将 Event Stream → State Stream 的映射
class CounterBloc {
final _eventController = StreamController<CounterEvent>();
final _stateController = StreamController<int>.broadcast();
Stream<int> get state => _stateController.stream;
Sink<CounterEvent> get eventSink => _eventController.sink;
CounterBloc() : _state = 0 {
_eventController.stream.listen(_mapEventToState);
}
int _state;
void _mapEventToState(CounterEvent event) {
switch (event) {
case CounterEvent.increment:
_state++;
case CounterEvent.decrement:
_state--;
}
_stateController.add(_state);
}
void dispose() {
_eventController.close();
_stateController.close();
}
}
enum CounterEvent { increment, decrement }
底层原理
Stream 的事件循环调度
Stream 通过 Zone 和 _StreamImpl 内部类实现调度。当调用 add(data) 时:
- 检测当前是否有 pending 的事件
- 如果 Stream 是同步的(
sync: true),在当前调用栈中立即执行 listener - 如果 Stream 是异步的,将事件放入微任务队列
1
2
3
4
5
6
7
8
9
10
11
// 同步 Stream 的行为
final syncController = StreamController<String>.broadcast(sync: true);
syncController.stream.listen(print);
syncController.add('立即'); // 在当前栈帧中同步执行 print
print('之后'); // 结论:先打印'立即',再打印'之后'
// 异步 Stream 的行为
final asyncController = StreamController<String>.broadcast(sync: false);
asyncController.stream.listen(print);
asyncController.add('异步'); // 压入微任务队列
print('快'); // 结论:先打印'快',再打印'异步'
StreamIterator 与 await for 的实现
await for 在编译层面被展开为:
1
2
3
4
5
6
7
8
9
// await for (final value in stream) { ... }
// 等价于:
Future<void> _expanded(Stream<int> stream) async {
final iterator = StreamIterator(stream);
while (await iterator.moveNext()) {
final value = iterator.current;
// 循环体
}
}
StreamIterator.moveNext() 内部会在当前事件到达前挂起,通过 Completer 实现跨事件交付的桥梁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class StreamIterator<T> {
final Stream<T> _stream;
StreamSubscription<T>? _subscription;
Completer<bool>? _completer;
T? _current;
Future<bool> moveNext() async {
_completer = Completer<bool>();
_subscription ??= _stream.listen(
(data) {
_current = data;
_completer?.complete(true);
_subscription?.pause(); // 每次收到事件后暂停,等待调用 moveNext
},
onDone: () => _completer?.complete(false),
);
_subscription?.resume();
return _completer!.future;
}
T get current => _current!;
}
与 RxDart 的关系
RxDart 是 Dart 官方的响应式扩展库,在原生 Stream 基础上增加了:
BehaviorSubject:带初始值、订阅后立即获得最新值ReplaySubject:缓存历史事件combineLatest、zip、merge等组合操作符debounce、throttle、buffer等高级操作符
高频面试题解析
Q1:Stream 和 Future 的根本区别?
| 维度 | Future | Stream |
|---|---|---|
| 事件数量 | 0 或 1 个 | 0 到 N 个 |
| 订阅方式 | then/catchError | listen/await for |
| 多次订阅 | 始终可用 | 单订阅 Stream 仅有一次 |
| 取消 | 无法取消 | cancel() 可随时取消 |
| 变换 | 一个值变换 | 管道式变换链 |
Q2:如何将一个 Stream 转换为 Future?
1
2
3
4
5
6
7
8
9
Stream<int> numberStream = Stream.fromIterable([1, 2, 3]);
// 转单一 Future(取第一个或最后一个)
Future<int> first = numberStream.first; // 1
Future<int> last = numberStream.last; // 3
Future<int> single = numberStream.single; // 抛出异常(多于1个)
// 收集为 List
Future<List<int>> asList = numberStream.toList();
Q3:Stream 的 pause/resume 机制有什么注意事项?
1
2
3
4
5
6
7
8
9
10
11
12
final controller = StreamController<int>();
final sub = controller.stream.listen((v) {
print('收到: $v');
});
controller.add(1); // 收到
sub.pause();
controller.add(2); // 暂存但不传递
controller.add(3); // 暂存但不传递
sub.resume(); // 收到 2 和 3(合并为一次回调)
// ⚠️ pause 后数据会积压在缓冲区,长时间暂停可能导致内存问题
Q4:如何处理 Stream 的背压(Backpressure)?
推荐策略:
- Buffer 控制:
stream.buffer(Duration)或stream.throttle - 转换器减速:实现
StreamTransformer在数据流中插入暂停点 - 监听回调中异步:
stream.asyncMap(handleAsync)确保每次处理完成后才继续 - 背压信号:消费端通过 Stream 通道向生产端发送控制信号
总结与扩展
核心要点
- Stream 是 Future 在时间维度上的自然延伸——从单一事件到事件序列
- Dart 原生 Stream 的设计足够完备,大多数场景无需额外库
async*+await for语法糖让流式代码与同步代码风格一致- StreamController 的细粒度控制(onListen/onPause/onCancel)让自定义数据源变得简单
- Broadcast Stream 是多订阅场景的必备工具
扩展阅读
- Dart 官方教程: Streams
- RxDart: ReactiveX for Dart
- Flutter Bloc: Bloc State Management
- 经典文章: The Boring Flutter Development Team - Streams
下一步
深入理解 Stream 后,下一篇文章将探讨 Dart 中真正实现并行计算的工具——Isolate 隔离机制,以及它与 Stream 如何配合完成高性能计算任务。