什么时候该用 Channel.CreateBounded
?
当你需要控制内存水位、防止生产者把消费者“拖垮”时,必须选有界通道。比如:实时日志采集服务中,上游每秒写入 5000 条事件,下游单个消费者每秒最多处理 800 条——这时若用无界通道,几秒内就可能堆积数万条未消费消息,
OutOfMemoryException风险极高。
BoundedChannelOptions必须指定
Capacity(如
new BoundedChannelOptions(1000)),这是硬上限 满时行为由
FullMode决定:
Wait(默认,写操作挂起)、
DropOldest(丢老数据)、
DropNewest(丢新数据)或
DropWrite(直接返回 false 不阻塞) 若设
SingleWriter = true,可省去内部锁开销,适合单线程生产场景 注意:
Capacity是元素个数,不是字节数;装的是
string还是
byte[],内存占用差异巨大
为什么 Channel.CreateUnbounded
不等于“随便用”?
它只是没有显式容量限制,但不意味着能无限吃内存。底层仍是托管堆上的对象集合,一旦生产速度持续高于消费速度,GC 压力飙升,最终照样 OOM。它真正的价值在于简化背压逻辑,而非纵容失控。
UnboundedChannelOptions支持
SingleReader和
AllowSynchronousContinuations等调优项,但没
Capacity字段
AllowSynchronousContinuations = false可避免回调在 I/O 线程上同步执行,防止线程池饥饿(尤其在 ASP.NET Core 中很重要) 典型适用场景:短生命周期任务管道(如 HTTP 请求进 → 验证 → 缓存检查 → 响应),全程耗时 误用高发点:拿它替代消息队列(如 RabbitMQ/Kafka)做跨进程/跨机器通信——
Channel仅限进程内,崩溃即丢失
WriteAsync
在两种模式下行为差异极大
表面看都是 await 写入,但背后调度逻辑完全不同:
有界通道下,await channel.Writer.WriteAsync(x)可能**长时间挂起**(取决于
FullMode)。例如
FullMode.Wait时,若队列已满,协程会挂起直到消费者取走至少一个元素 无界通道下,
WriteAsync几乎总能立即返回(除非内存彻底耗尽),但代价是:你失去了对写入节奏的主动控制权 若想统一处理“写失败”,有界通道可配合
TryWrite(非 await,返回 bool);无界通道没有等效 API,只能靠异常捕获或提前监控内存 务必记得:无论哪种模式,写完都要调用
channel.Writer.Complete(),否则
ReadAllAsync永远不会结束
别忽略 Reader/Writer 的并发安全配置
默认创建的 Channel 允许多生产者多消费者,但如果你实际只用单线程生产+单线程消费,却没关掉多余同步开销,性能反而下降。
有界通道中,SingleWriter = true+
SingleReader = true可让内部跳过大部分原子操作和锁,吞吐量提升 20%~40% 无界通道中,
SingleReader = true同样有效,但
SingleWriter = true效果有限(因无界实现本身已偏向无锁) 错误示范:
Channel.CreateBounded<int>(100)</int>什么选项都不设,结果在高并发写入时出现
InvalidOperationException: Channel is closed—— 很可能是多个 Writer 同时调用
Complete()导致的竞态 真正难的不是选
Bounded还是
Unbounded,而是预估好你的
Capacity值和
FullMode策略是否匹配业务语义。比如金融交易系统丢数据不可接受,那宁可
Wait卡住也不能
DropOldest;而监控指标上报丢了最近几秒数据问题不大,但卡住会导致整个采集链路雪崩。
