优先选 CreateBounded(capacity),因其可控内存上限;无界通道易致OOM;有界通道需配置 FullMode 避免生产者挂起;Consumer 必须依赖 Complete() 才能安全退出 await foreach。

Channel.CreateUnbounded 和 CreateBounded 选哪个?
绝大多数新手一上来就用 CreateUnbounded,觉得“不用管容量,省事”,但这是最容易埋雷的选择。无界通道会无限缓存数据,一旦消费者卡住或处理变慢,内存会持续上涨,最终 OOM——尤其在日志采集、传感器流等高吞吐场景下,这问题来得又快又静默。
真正该按需选:
• 用 CreateBounded 控制内存上限,比如 Channel.CreateBounded;
• 显式传 BoundedChannelOptions 配置 FullMode,避免默认 Wait 导致生产者协程被挂起太久;
• 真需要无界语义(如内部管道、短生命周期任务),才用 CreateUnbounded,但务必搭配超时或背压检测逻辑。
WriteAsync 写不进去?检查 Complete() 和 FullMode
常见现象:生产者调用 await channel.Writer.WriteAsync(item) 后卡住不动,或者直接抛出 InvalidOperationException: The channel has been closed。
- 先确认是否误调了
channel.Writer.Complete()—— 一旦调用,后续所有写入都会失败; - 如果是有界通道且满载,
WriteAsync默认会Wait,直到有空位;若你希望快速失败,改用TryWrite或设FullMode = BoundedChannelFullMode.DropWrite; -
DropNewest和DropOldest适用于滑动窗口类场景(如最近 100 条状态),但注意:丢弃行为是静默的,不抛异常,也不通知生产者。
Consumer 怎么安全读完所有数据?别漏掉 Complete()
消费者用 await foreach (var item in channel.Reader.ReadAllAsync()) 是最简洁的方式,但它**依赖生产者显式调用 channel.Writer.Complete()**。漏掉这句,循环永远不会退出——不是卡死,而是永远挂起等待下一条。
更健壮的做法(尤其多生产者):
• 每个生产者完成时调用 channel.Writer.TryComplete()(线程安全,多次调用无副作用);
• 消费者可结合 channel.Reader.Completion 监听异常;
• 若需手动控制读取节奏,用 await channel.Reader.ReadAsync() + TryRead 判断是否已空或已关闭。
Channel 不是万能队列:和 Dataflow、BlockingCollection 的关键区别
很多人把 Channel 当作“新版 BlockingCollection”,其实它定位更轻:只负责**异步、线程安全的数据暂存与传递**,不带处理逻辑、不支持广播、不内置限流策略。
- 要 pipeline 式加工(如 TransformBlock → ActionBlock),用
System.Threading.Tasks.Dataflow; - 要同步阻塞+取消支持+枚举器兼容,
BlockingCollection仍适用(尤其老项目或混合同步/异步场景); -
Channel的优势在纯异步高吞吐:比如 ASP.NET Core 中接 HTTP body 流、gRPC server streaming、模拟 PLC 通信——这些场景里,它比 Dataflow 少一层封装,分配更少对象,GC 压力更低。
最常被忽略的一点:Channel 的 Reader 和 Writer 是分离对象,可以跨 Task、跨线程自由传递,但**不能重复使用已 Complete 的 Writer,也不能对已 Complete 的 Reader 调用 ReadAsync**——这类错误不会立刻崩溃,而是在 await 时才抛异常,调试时容易绕远路。
