首选 blockingcollection,因其底层封装线程安全集合与阻塞/取消语义,自动处理取空阻塞、满阻塞和取消通知,支持有界缓冲、自动完成信号及统一异常处理。

直接用 BlockingCollection<t></t>,别手写锁和队列,它就是为这场景设计的。 手动用 Monitor、AutoResetEvent 或 ConcurrentQueue<t></t> + 自旋等待,容易出死锁、假唤醒、吞异常或资源泄漏。
为什么首选 BlockingCollection<t></t>
它底层封装了线程安全的集合(默认 ConcurrentQueue<t></t>)+ 阻塞/取消语义,自动处理“取空时阻塞”“满时阻塞”“取消通知”三件事。你不用管 WaitOne() 是否被中断、PulseAll() 是否漏通知、消费者是否在 Take() 前就被 CancellationToken 中止。
-
BlockingCollection<t></t>构造时可传入boundedCapacity实现有界缓冲区,避免内存无限增长 - 调用
CompleteAdding()后,后续GetConsumingEnumerable()会自然退出循环,无需额外标志位 - 所有操作(
Add()、Take()、TryAdd())都响应CancellationToken,异常类型统一为OperationCanceledException
GetConsumingEnumerable() 是最简消费写法
别再写 while (true) { try { item = collection.Take(); } catch ... }。用 GetConsumingEnumerable() 让 foreach 自动处理完成信号和取消:
var collection = new BlockingCollection<string>(boundedCapacity: 100);
// 生产者
Task.Run(() => {
for (int i = 0; i
<ul>
<li>如果没调用 <code>CompleteAdding()</code>,<code>GetConsumingEnumerable()</code> 会永远等待新项,程序无法退出</li>
<li>该枚举器内部已处理 <code>CancellationToken</code>,若需取消,请传入构造时指定的 token(通过 <code>new BlockingCollection<t>(collection, token)</t></code>)</li>
<li>不支持 LINQ 方法如 <code>.Where()</code> 或 <code>.Skip()</code>,它是只读流式迭代器</li>
</ul>
<h3>多消费者共用一个 <code>BlockingCollection</code> 时要注意什么</h3>
<p>多个 <code>foreach</code> 循环同时遍历同一个 <code>GetConsumingEnumerable()</code> 实例?不行——每个消费者必须调用自己的 <code>GetConsumingEnumerable()</code>,否则会共享迭代状态导致漏项或异常。</p>
<ul>
<li>正确做法:每个消费者线程独立调用 <code>collection.GetConsumingEnumerable()</code>
</li>
<li>错误写法:<code>var enu = collection.GetConsumingEnumerable(); Task.Run(() => foreach(var x in enu) {...}); Task.Run(() => foreach(var x in enu) {...});</code> —— 第二个 foreach 立即结束</li>
<li>若需精确控制并发数(比如最多 3 个消费者),用 <code>Parallel.ForEach(collection.GetConsumingEnumerable(), ...)</code> 并设 <code>MaxDegreeOfParallelism</code>,但注意它不保证顺序且异常会聚合抛出</li>
</ul>
<p>真正难的是边界情况:生产者中途崩溃未调用 <code>CompleteAdding()</code>、消费者处理逻辑抛异常未捕获导致整个 foreach 退出、或 <code>boundedCapacity</code> 设太小造成生产者频繁阻塞影响上游吞吐。这些没法靠模式本身解决,得靠超时重试、外部健康检查和日志埋点来兜底。</p></string>











