Dart Stream 流式编程全面指南
概述
本指南基于官方 API 文档和实践经验,全面深入地探讨了 Dart 中的 Stream 流式编程。Stream 是 Dart 处理异步事件序列的核心工具,广泛应用于文件操作、网络请求、用户交互等场景。
目录
- Stream 基础概念
- Stream 创建方法
- Stream 变换操作
- Stream 监听和订阅
- StreamController 详解
- 错误处理和资源管理
- 高级模式和最佳实践
- 实际应用场景
- 性能优化建议
- 常见问题和解决方案
Stream 基础概念
什么是 Stream
Stream 是一个异步事件序列,可以想象为一个水管,数据从一端流入,从另一端流出。与 Future 处理单个异步操作不同,Stream 可以处理连续的异步事件。
Stream 的特点
- 异步性: Stream 中的数据是异步产生和消费的
- 惰性: Stream 只有在被监听时才开始产生数据
- 可组合: 可以通过各种操作符组合和变换 Stream
- 背压处理: 可以处理生产者和消费者速度不匹配的情况
单订阅流 vs 广播流
单订阅流 (Single-subscription Stream):
- 只能被监听一次
- 适用于文件读取、网络请求等一次性操作
- 默认创建的 Stream 都是单订阅流
广播流 (Broadcast Stream):
- 可以被多个监听器同时监听
- 适用于事件通知、状态更新等需要多方监听的场景
- 通过
asBroadcastStream()转换或直接创建
Stream 创建方法
1. 从现有数据创建
// 从可迭代对象创建
Stream<int> stream1 = Stream.fromIterable([1, 2, 3, 4, 5]);
// 创建单个值的流
Stream<String> stream2 = Stream.value('Hello');
// 创建空流
Stream<dynamic> stream3 = Stream.empty();
// 创建错误流
Stream<int> stream4 = Stream.error('Error occurred');
2. 从 Future 创建
// 从单个 Future 创建
Stream<String> stream1 = Stream.fromFuture(
Future.delayed(Duration(seconds: 1), () => 'Done')
);
// 从多个 Future 创建
Stream<int> stream2 = Stream.fromFutures([
Future.delayed(Duration(seconds: 1), () => 1),
Future.delayed(Duration(seconds: 2), () => 2),
]);
3. 周期性 Stream
// 每秒产生一个递增的数字
Stream<int> periodicStream = Stream.periodic(
Duration(seconds: 1),
(count) => count,
);
// 带有时间戳的周期流
Stream<DateTime> timeStream = Stream.periodic(
Duration(seconds: 1),
(_) => DateTime.now(),
);
4. 异步生成器
Stream<int> asyncGenerator() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i * i; // 产出平方数
}
}
Stream 变换操作
基础变换操作符
| 操作符 | 功能 | 示例 |
|---|---|---|
map | 转换每个元素 | stream.map((x) => x * 2) |
where | 过滤元素 | stream.where((x) => x > 5) |
expand | 展开元素 | stream.expand((x) => [x, x]) |
take | 取前 n 个元素 | stream.take(5) |
skip | 跳过前 n 个元素 | stream.skip(3) |
distinct | 去重 | stream.distinct() |
聚合操作
// fold - 折叠操作(有初始值)
var sum = await stream.fold<int>(0, (prev, element) => prev + element);
// reduce - 归约操作(无初始值)
var max = await stream.reduce((a, b) => a > b ? a : b);
// 条件检查
var hasEven = await stream.any((x) => x % 2 == 0);
var allPositive = await stream.every((x) => x > 0);
异步变换
// asyncMap - 异步映射
stream.asyncMap((x) async {
await Future.delayed(Duration(milliseconds: 100));
return x * x;
});
// asyncExpand - 异步展开
stream.asyncExpand((x) async* {
for (int i = 0; i < x; i++) {
yield i;
}
});
Stream 监听和订阅
1. 使用 listen 方法
StreamSubscription subscription = stream.listen(
(data) => print('Data: $data'), // 处理数据
onError: (error) => print('Error: $error'), // 处理错误
onDone: () => print('Stream completed'), // 流完成
cancelOnError: false, // 遇到错误是否取消
);
2. 使用 await for
void processStream() async {
try {
await for (var data in stream) {
print('Processing: $data');
// 可以使用 break 提前退出
if (data > 10) break;
}
} catch (e) {
print('Error: $e');
}
}
3. 订阅控制
// 暂停订阅
subscription.pause();
// 恢复订阅
subscription.resume();
// 取消订阅
await subscription.cancel();
// 检查订阅状态
bool isPaused = subscription.isPaused;
StreamController 详解
基本用法
// 创建 StreamController
var controller = StreamController<int>();
// 监听流
controller.stream.listen((data) => print('Data: $data'));
// 添加数据
controller.sink.add(1);
controller.sink.add(2);
// 添加错误
controller.sink.addError('Something went wrong');
// 关闭流
controller.close();
广播 StreamController
// 创建广播控制器
var broadcastController = StreamController<String>.broadcast();
// 多个监听器
broadcastController.stream.listen((data) => print('Listener 1: $data'));
broadcastController.stream.listen((data) => print('Listener 2: $data'));
// 发送数据(所有监听器都会收到)
broadcastController.sink.add('Hello');
带回调的 StreamController
var controller = StreamController<String>(
onListen: () => print('监听器开始监听'),
onPause: () => print('监听器暂停'),
onResume: () => print('监听器恢复'),
onCancel: () => print('监听器取消'),
);
错误处理和资源管理
错误处理策略
1. 在监听时处理错误
stream.listen(
(data) => processData(data),
onError: (error) {
if (error is TimeoutException) {
// 处理超时错误
handleTimeout();
} else if (error is SocketException) {
// 处理网络错误
handleNetworkError();
} else {
// 处理其他错误
handleGenericError(error);
}
},
cancelOnError: false, // 不因错误而取消订阅
);
2. 使用 handleError
stream
.handleError((error) {
logError(error);
// 可以返回替代值或重新抛出错误
})
.listen((data) => print(data));
3. 错误恢复和重试
Stream<T> retryStream<T>(Stream<T> Function() streamFactory, int maxRetries) {
return streamFactory().handleError((error) {
if (maxRetries > 0) {
print('重试中... 剩余次数: $maxRetries');
return retryStream(streamFactory, maxRetries - 1);
} else {
throw error;
}
});
}
资源管理最佳实践
Future<void> processWithResourceManagement() async {
StreamSubscription? subscription;
Timer? timeout;
try {
subscription = stream.listen(processData);
// 设置超时
timeout = Timer(Duration(seconds: 30), () {
subscription?.cancel();
throw TimeoutException('Stream processing timeout');
});
await subscription.asFuture();
} catch (e) {
print('Error: $e');
} finally {
// 清理资源
timeout?.cancel();
await subscription?.cancel();
}
}
高级模式和最佳实践
1. 流合并和组合
// 合并多个流
Stream<T> mergeStreams<T>(List<Stream<T>> streams) async* {
var subscriptions = <StreamSubscription<T>>[];
var controller = StreamController<T>();
var completedCount = 0;
for (var stream in streams) {
var subscription = stream.listen(
controller.add,
onError: controller.addError,
onDone: () {
completedCount++;
if (completedCount == streams.length) {
controller.close();
}
},
);
subscriptions.add(subscription);
}
await for (var data in controller.stream) {
yield data;
}
}
// 拉链合并
Stream<List<T>> zipStreams<T>(List<Stream<T>> streams) async* {
var values = List<T?>.filled(streams.length, null);
var controllers = <StreamController<T>>[];
// 实现拉链逻辑...
}
2. 背压处理
// 缓冲流实现
class BufferedStream<T> {
final Stream<T> source;
final int bufferSize;
final Queue<T> _buffer = Queue<T>();
BufferedStream(this.source, {required this.bufferSize});
Stream<T> get stream async* {
await for (var data in source) {
_buffer.add(data);
if (_buffer.length > bufferSize) {
_buffer.removeFirst(); // 丢弃旧数据
}
yield data;
}
}
}
3. 限流和采样
// 限流实现
Stream<T> rateLimit<T>(Stream<T> source, Duration interval) async* {
T? lastData;
var timer = Timer.periodic(interval, (timer) {
if (lastData != null) {
// 输出最新数据
}
});
await for (var data in source) {
lastData = data;
}
}
// 防抖动实现
Stream<T> debounce<T>(Stream<T> source, Duration duration) async* {
Timer? debounceTimer;
T? lastData;
await for (var data in source) {
lastData = data;
debounceTimer?.cancel();
debounceTimer = Timer(duration, () {
if (lastData != null) {
// 输出防抖后的数据
}
});
}
}
4. 缓存和重播
// 缓存流实现
class CachedStream<T> {
final Stream<T> source;
List<T>? _cachedData;
bool _isLoaded = false;
CachedStream(this.source);
Stream<T> get stream {
if (_isLoaded && _cachedData != null) {
return Stream.fromIterable(_cachedData!);
}
return source.toList().asStream().asyncExpand((list) {
_cachedData = list;
_isLoaded = true;
return Stream.fromIterable(list);
});
}
}
实际应用场景
1. 网络请求处理
class ApiService {
Stream<List<User>> getUsers() async* {
try {
var response = await http.get(Uri.parse('/api/users'));
var users = parseUsers(response.body);
yield users;
// 可以继续发出更新的数据
await for (var update in listenForUserUpdates()) {
yield update;
}
} catch (e) {
yield* Stream.error(e);
}
}
Stream<User> listenForUserUpdates() {
// WebSocket 或 SSE 实现
return webSocketStream.map((data) => User.fromJson(data));
}
}
2. 文件处理
Stream<String> readFileLines(String filePath) async* {
var file = File(filePath);
var inputStream = file.openRead();
await for (var line in inputStream
.transform(utf8.decoder)
.transform(LineSplitter())) {
yield line;
}
}
// 处理大文件
void processLargeFile(String filePath) async {
await for (var line in readFileLines(filePath)) {
// 逐行处理,内存占用小
await processLine(line);
}
}
3. 用户界面状态管理
class CounterBloc {
final _countController = StreamController<int>();
final _eventController = StreamController<CounterEvent>();
Stream<int> get count => _countController.stream;
Sink<CounterEvent> get events => _eventController.sink;
CounterBloc() {
_eventController.stream.listen(_handleEvent);
}
void _handleEvent(CounterEvent event) {
switch (event) {
case IncrementEvent():
_currentCount++;
break;
case DecrementEvent():
_currentCount--;
break;
}
_countController.add(_currentCount);
}
void dispose() {
_countController.close();
_eventController.close();
}
}
4. 实时数据监控
class SystemMonitor {
Stream<SystemMetrics> monitorSystem() async* {
while (true) {
var metrics = SystemMetrics(
cpuUsage: await getCpuUsage(),
memoryUsage: await getMemoryUsage(),
diskUsage: await getDiskUsage(),
timestamp: DateTime.now(),
);
yield metrics;
await Future.delayed(Duration(seconds: 1));
}
}
Stream<Alert> detectAnomalies() {
return monitorSystem()
.where((metrics) => metrics.isAbnormal())
.map((metrics) => Alert.fromMetrics(metrics));
}
}
性能优化建议
1. 避免内存泄漏
class StreamManager {
final List<StreamSubscription> _subscriptions = [];
void addSubscription(StreamSubscription subscription) {
_subscriptions.add(subscription);
}
Future<void> dispose() async {
for (var subscription in _subscriptions) {
await subscription.cancel();
}
_subscriptions.clear();
}
}
2. 合理使用缓冲区
// 避免无限制的缓冲
stream
.buffer(Duration(seconds: 1)) // 按时间缓冲
.listen(processBatch);
// 或者按数量缓冲
stream
.bufferCount(100) // 每100个元素一批
.listen(processBatch);
3. 选择合适的监听模式
// 对于一次性操作,使用单订阅流
Stream<String> oneTimeOperation() async* {
yield await expensiveOperation();
}
// 对于事件通知,使用广播流
final eventBus = StreamController<Event>.broadcast();
4. 及时取消不需要的订阅
class PageController {
StreamSubscription? _dataSubscription;
void onPageLoad() {
_dataSubscription = dataStream.listen(updateUI);
}
void onPageUnload() {
_dataSubscription?.cancel();
_dataSubscription = null;
}
}
常见问题和解决方案
1. "Stream已经被监听"错误
问题: 尝试监听已经被监听过的单订阅流
解决方案:
// 方法1: 转换为广播流
var broadcastStream = singleStream.asBroadcastStream();
broadcastStream.listen(listener1);
broadcastStream.listen(listener2);
// 方法2: 使用 StreamSplitter
var splitter = StreamSplitter(singleStream);
splitter.split().listen(listener1);
splitter.split().listen(listener2);
2. 内存泄漏
问题: 忘记取消 StreamSubscription 导致内存泄漏
解决方案:
class ResourceManager {
final List<StreamSubscription> _subscriptions = [];
void addStream<T>(Stream<T> stream, void Function(T) onData) {
var subscription = stream.listen(onData);
_subscriptions.add(subscription);
}
Future<void> cleanup() async {
await Future.wait(_subscriptions.map((s) => s.cancel()));
_subscriptions.clear();
}
}
3. 错误处理不当
问题: 错误导致整个流停止
解决方案:
stream
.handleError((error) {
// 记录错误但不重新抛出
logger.error('Stream error: $error');
})
.where((data) => data != null) // 过滤掉错误产生的空值
.listen(processData);
4. 背压问题
问题: 生产者速度远快于消费者
解决方案:
// 方法1: 使用缓冲和采样
stream
.sample(Duration(milliseconds: 100)) // 采样
.listen(slowProcessor);
// 方法2: 使用背压感知的流
class BackpressureAwareStream<T> {
final Stream<T> source;
final int maxBacklog;
BackpressureAwareStream(this.source, {this.maxBacklog = 100});
Stream<T> get stream async* {
var buffer = Queue<T>();
await for (var data in source) {
if (buffer.length < maxBacklog) {
buffer.add(data);
} else {
// 丢弃旧数据或暂停生产者
buffer.removeFirst();
buffer.add(data);
}
if (buffer.isNotEmpty) {
yield buffer.removeFirst();
}
}
}
}
总结
Dart Stream 是一个功能强大的异步编程工具,通过本指南的学习,您应该能够:
- 理解 Stream 的基本概念和使用场景
- 掌握各种 Stream 创建方法,选择最适合的方式
- 熟练使用变换操作符进行数据处理
- 正确处理错误和管理资源,避免内存泄漏
- 应用高级模式解决复杂的异步编程问题
- 在实际项目中合理使用 Stream,提高代码质量
最佳实践总结
- 选择合适的 Stream 类型: 一次性操作用单订阅流,事件通知用广播流
- 及时清理资源: 始终取消不需要的订阅
- 合理处理错误: 使用
handleError和onError进行错误处理 - 避免阻塞: 在异步操作中使用
await而不是同步等待 - 性能优化: 使用缓冲、采样等技术处理高频数据
- 代码组织: 将复杂的 Stream 逻辑封装在专门的类中
通过掌握这些知识和技巧,您将能够在 Dart 和 Flutter 开发中高效地使用 Stream 进行异步编程,构建响应式和高性能的应用程序。