Dart Stream 流式编程全面指南

74 阅读5分钟

Dart Stream 流式编程全面指南

概述

本指南基于官方 API 文档和实践经验,全面深入地探讨了 Dart 中的 Stream 流式编程。Stream 是 Dart 处理异步事件序列的核心工具,广泛应用于文件操作、网络请求、用户交互等场景。

目录

  1. Stream 基础概念
  2. Stream 创建方法
  3. Stream 变换操作
  4. Stream 监听和订阅
  5. StreamController 详解
  6. 错误处理和资源管理
  7. 高级模式和最佳实践
  8. 实际应用场景
  9. 性能优化建议
  10. 常见问题和解决方案

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 是一个功能强大的异步编程工具,通过本指南的学习,您应该能够:

  1. 理解 Stream 的基本概念和使用场景
  2. 掌握各种 Stream 创建方法,选择最适合的方式
  3. 熟练使用变换操作符进行数据处理
  4. 正确处理错误和管理资源,避免内存泄漏
  5. 应用高级模式解决复杂的异步编程问题
  6. 在实际项目中合理使用 Stream,提高代码质量

最佳实践总结

  1. 选择合适的 Stream 类型: 一次性操作用单订阅流,事件通知用广播流
  2. 及时清理资源: 始终取消不需要的订阅
  3. 合理处理错误: 使用 handleErroronError 进行错误处理
  4. 避免阻塞: 在异步操作中使用 await 而不是同步等待
  5. 性能优化: 使用缓冲、采样等技术处理高频数据
  6. 代码组织: 将复杂的 Stream 逻辑封装在专门的类中

通过掌握这些知识和技巧,您将能够在 Dart 和 Flutter 开发中高效地使用 Stream 进行异步编程,构建响应式和高性能的应用程序。