在多线程编程的世界里,生产者-消费者模式就像是一个繁忙的餐厅厨房。厨师们(生产者)不断制作菜肴,服务员们(消费者)则负责将菜品送到客人桌上。BlockingCollection
这个来自 System.Collections.Concurrent 命名空间的线程安全集合,本质上是一个提供了阻塞和边界能力的包装器。它最常见的用法是包装一个 IProducerConsumerCollection
提示:虽然默认使用 ConcurrentQueue
实现 FIFO 行为,但在订单处理等场景中,使用 ConcurrentStack 实现 LIFO 可能更符合业务需求。
BlockingCollection
csharp复制// 典型的生产者代码
public void Produce(BlockingCollection<int> collection)
{
while (true)
{
int item = GenerateItem();
collection.Add(item); // 如果集合已满,将在此阻塞
}
}
// 典型的消费者代码
public void Consume(BlockingCollection<int> collection)
{
while (true)
{
int item = collection.Take(); // 如果集合为空,将在此阻塞
ProcessItem(item);
}
}
设置 BoundedCapacity 就像是给生产线安装了一个缓冲调节阀。当生产速度超过消费速度时,这个限制会触发背压(backpressure),自动减缓生产者的速度。这在防止内存溢出方面特别有用:
csharp复制// 创建容量为100的阻塞集合
var collection = new BlockingCollection<int>(boundedCapacity: 100);
警告:如果不设置边界容量,生产者持续快速添加而消费者处理缓慢,可能导致内存无限增长最终引发 OutOfMemoryException。
不同的底层容器就像不同的传送带类型:
csharp复制// 使用 ConcurrentStack 作为底层存储
var stackCollection = new BlockingCollection<int>(new ConcurrentStack<int>(), boundedCapacity: 50);
有时一个厨房需要多个传菜窗口。我们可以使用 AddToAny() 和 TakeFromAny() 同时操作多个集合:
csharp复制var collections = new[]
{
new BlockingCollection<int>(boundedCapacity: 10),
new BlockingCollection<int>(boundedCapacity: 10)
};
// 生产者可以向任意集合添加
BlockingCollection<int>.AddToAny(collections, 42);
// 消费者可以从任意集合获取
int item = BlockingCollection<int>.TakeFromAny(collections, out int collectionIndex);
就像餐厅打烊时需要通知所有服务员下班一样,我们需要正确标记集合完成:
csharp复制// 生产者完成添加后调用
collection.CompleteAdding();
// 消费者检查是否完成
while (!collection.IsCompleted)
{
try
{
var item = collection.Take();
Process(item);
}
catch (InvalidOperationException)
{
// 集合已完成且为空
break;
}
}
csharp复制foreach (var item in collection.GetConsumingEnumerable())
{
Process(item);
}
csharp复制if (collection.TryAdd(item, timeout: TimeSpan.FromSeconds(30)))
{
// 添加成功
}
else
{
// 超时处理
}
csharp复制var cts = new CancellationTokenSource();
var token = cts.Token;
// 在另一个线程中
try
{
foreach (var item in collection.GetConsumingEnumerable(token))
{
Process(item);
}
}
catch (OperationCanceledException)
{
// 取消处理
}
死锁就像两个服务员在厨房门口互相谦让,结果谁都过不去。常见死锁场景:
csharp复制var collection = new BlockingCollection<int>(boundedCapacity: 1);
collection.Add(1); // 第一个元素
collection.Add(2); // 第二个元素会阻塞,因为没有消费者
// 如果下面这行在另一个线程中,但该线程无法启动...
var item = collection.Take(); // 永远不会执行
解决方案:确保消费者线程先启动,或使用 TryAdd 避免阻塞。
即使有容量限制,如果消费者处理异常导致无法消费,生产者最终也会被阻塞。诊断方法:
csharp复制// 监控集合计数
Debug.WriteLine($"当前集合项数: {collection.Count}, 是否完成: {collection.IsAddingCompleted}");
使用 PerformanceCounter 监控关键指标:
csharp复制var itemsAddedCounter = new PerformanceCounter(
categoryName: "BlockingCollection",
counterName: "Items Added/sec",
readOnly: false);
itemsAddedCounter.Increment();
我们曾用 BlockingCollection 构建高吞吐日志系统,处理日均10亿条日志:
csharp复制class LogProcessor
{
private readonly BlockingCollection<LogEntry> _logQueue =
new BlockingCollection<LogEntry>(boundedCapacity: 10000);
public void EnqueueLog(LogEntry log)
{
if (!_logQueue.TryAdd(log, TimeSpan.FromMilliseconds(100)))
{
// 队列满时的降级处理
WriteToFallbackStorage(log);
}
}
public void StartProcessing(int workerCount)
{
for (int i = 0; i < workerCount; i++)
{
Task.Run(() => ProcessLogs());
}
}
private void ProcessLogs()
{
foreach (var log in _logQueue.GetConsumingEnumerable())
{
try
{
// 实际处理逻辑
SaveToDatabase(log);
}
catch (Exception ex)
{
// 错误处理
}
}
}
}
关键优化点:
.NET Core 引入的 Channel
csharp复制// Channel 的类似实现
var channel = Channel.CreateBounded<int>(capacity: 100);
// 生产者
await channel.Writer.WriteAsync(42);
// 消费者
await foreach (var item in channel.Reader.ReadAllAsync())
{
Process(item);
}
BufferBlock
csharp复制var bufferBlock = new BufferBlock<int>(
new DataflowBlockOptions { BoundedCapacity = 100 });
// 生产者
await bufferBlock.SendAsync(42);
// 消费者
while (await bufferBlock.OutputAvailableAsync())
{
var item = await bufferBlock.ReceiveAsync();
Process(item);
}
选择建议:
使用 BenchmarkDotNet 测试不同场景下的性能:
| 操作 | 单线程 | 4生产者2消费者 |
|---|---|---|
| Add/Take (Queue) | 12ns | 24ns |
| TryAdd/TryTake | 15ns | 28ns |
| GetConsumingEnumerable | 18ns | 32ns |
容量设置:根据内存和延迟需求平衡
生产者/消费者比例:
批次处理:使用 GetConsumingEnumerable().Take(n) 批量处理
csharp复制// 批量获取100条处理
var batch = collection.GetConsumingEnumerable().Take(100).ToArray();
ProcessBatch(batch);
通过多个 BlockingCollection 实现优先级:
csharp复制var highPriority = new BlockingCollection<WorkItem>(100);
var normalPriority = new BlockingCollection<WorkItem>(100);
var collections = new[] { highPriority, normalPriority };
// 消费者
while (true)
{
var item = BlockingCollection<WorkItem>.TakeFromAny(
collections, out int collectionIndex);
Process(item);
}
使用 GetConsumingEnumerable() 和 LINQ 实现延迟过滤:
csharp复制var importantItems = collection.GetConsumingEnumerable()
.Where(item => item.IsImportant);
foreach (var item in importantItems)
{
ProcessImportant(item);
}
结合 MemoryMappedFile 实现跨进程生产者-消费者:
csharp复制// 生产者进程
using var mmf = MemoryMappedFile.CreateNew("SharedQueue", 1024 * 1024);
using var accessor = mmf.CreateViewAccessor();
var queue = new BlockingCollection<byte[]>();
// 将 queue 序列化到内存映射文件...
// 消费者进程
using var mmf = MemoryMappedFile.OpenExisting("SharedQueue");
// 从内存映射文件反序列化 queue...
while (!queue.IsCompleted)
{
var data = queue.Take();
Process(data);
}
连接多个 BlockingCollection 形成处理管道:
csharp复制var stage1 = new BlockingCollection<Data>(100);
var stage2 = new BlockingCollection<Data>(100);
// 第一阶段处理器
Task.Run(() =>
{
foreach (var data in stage1.GetConsumingEnumerable())
{
var processed = ProcessStage1(data);
stage2.Add(processed);
}
stage2.CompleteAdding();
});
// 第二阶段处理器
Task.Run(() =>
{
foreach (var data in stage2.GetConsumingEnumerable())
{
ProcessStage2(data);
}
});
通过 ConcurrentBag 实现工作窃取:
csharp复制var sharedWorkItems = new BlockingCollection<WorkItem>(
new ConcurrentBag<WorkItem>(),
boundedCapacity: 1000);
// 工作者线程
while (!sharedWorkItems.IsCompleted)
{
if (sharedWorkItems.TryTake(out var item))
{
Process(item);
}
else if (localQueue.TrySteal(out item)) // 从其他线程窃取
{
Process(item);
}
}
在 Watch 窗口监控关键属性:
collection.Countcollection.IsAddingCompletedcollection.IsCompleted条件断点:在 Take() 方法上设置条件断点,如 Count == 0
当出现死锁时,分析线程堆栈:
Add() 或 Take() 的线程IsAddingCompleted 状态BlockingCollection 会发出 ETW 事件:
csharp复制var listener = new BlockingCollectionEventListener();
listener.EventWritten += (sender, e) =>
{
Console.WriteLine($"Event: {e.EventName}, Count: {e.Payload["Count"]}");
};
csharp复制[Test]
public void TestBoundedCapacity()
{
var collection = new BlockingCollection<int>(1);
Assert.IsTrue(collection.TryAdd(1)); // 应成功
Assert.IsFalse(collection.TryAdd(2)); // 应失败
}
csharp复制[Test]
public void TestCompleteAdding()
{
var collection = new BlockingCollection<int>();
collection.CompleteAdding();
Assert.Throws<InvalidOperationException>(() => collection.Add(1));
}
使用 Parallel.For 模拟高并发:
csharp复制[Test]
public void StressTest()
{
var collection = new BlockingCollection<int>(1000);
int itemCount = 1000000;
// 生产者
Parallel.For(0, itemCount, i => collection.Add(i));
// 消费者
int consumed = 0;
Parallel.For(0, 4, _ =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Interlocked.Increment(ref consumed);
}
});
Assert.AreEqual(itemCount, consumed);
}
替换传统 Queue + lock 模式:
csharp复制// 旧代码
lock (_syncRoot)
{
_queue.Enqueue(item);
Monitor.Pulse(_syncRoot);
}
// 新代码
_blockingCollection.Add(item);
csharp复制try
{
collection.Add(item);
}
catch (InvalidOperationException)
{
// 集合已完成添加
Log("Cannot add more items");
}
csharp复制if (!collection.IsCompleted)
{
// 这里可能有竞态条件
var item = collection.Take();
}
正确做法:
csharp复制try
{
var item = collection.Take();
Process(item);
}
catch (InvalidOperationException)
{
// 集合已完成
}
创建基于链表的并发集合:
csharp复制class ConcurrentLinkedList<T> : IProducerConsumerCollection<T>
{
// 实现必要接口方法
public bool TryAdd(T item) { ... }
public bool TryTake(out T item) { ... }
// 其他成员...
}
var customCollection = new BlockingCollection<T>(
new ConcurrentLinkedList<T>());
添加日志功能的装饰器:
csharp复制class LoggingBlockingCollection<T> : BlockingCollection<T>
{
public override void Add(T item)
{
Log($"Adding item: {item}");
base.Add(item);
}
}
实现 IDisposable 模式:
csharp复制class ResourceProcessor : IDisposable
{
private readonly BlockingCollection<Resource> _collection;
private bool _disposed;
public void Dispose()
{
if (_disposed) return;
_collection.CompleteAdding();
// 等待消费者完成
while (_collection.Count > 0) Thread.Sleep(10);
_disposed = true;
}
}
csharp复制var pool = new ObjectPool<LargeObject>(() => new LargeObject());
var collection = new BlockingCollection<LargeObject>();
// 生产者
var obj = pool.Get();
collection.Add(obj);
// 消费者
var item = collection.Take();
// 使用后归还
pool.Return(item);
高频交易系统中的使用模式:
csharp复制// 行情数据处理器
class MarketDataProcessor
{
private readonly BlockingCollection<TickData> _ticks;
public void OnTick(TickData tick)
{
if (!_ticks.TryAdd(tick, TimeSpan.FromMilliseconds(1)))
{
// 超时意味着系统过载
Throttle();
}
}
private void ProcessTicks()
{
foreach (var tick in _ticks.GetConsumingEnumerable())
{
ExecuteTradingStrategy(tick);
}
}
}
MMO 游戏中的消息处理:
csharp复制class GameSession
{
private readonly BlockingCollection<PlayerCommand> _commands;
public void EnqueueCommand(PlayerCommand cmd)
{
_commands.Add(cmd);
}
private void ProcessCommands()
{
foreach (var cmd in _commands.GetConsumingEnumerable())
{
try
{
cmd.Execute();
}
catch (GameRuleException ex)
{
SendError(cmd.PlayerId, ex.Message);
}
}
}
}
对于值类型元素,考虑内存紧凑布局:
csharp复制[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TradeRecord
{
public long Timestamp;
public decimal Price;
// 其他字段...
}
var collection = new BlockingCollection<TradeRecord>();
批量处理提高缓存命中率:
csharp复制var batch = new TradeRecord[100];
int index = 0;
foreach (var trade in collection.GetConsumingEnumerable())
{
batch[index++] = trade;
if (index == batch.Length)
{
ProcessBatch(batch);
index = 0;
}
}
错误模式:
csharp复制// 无限制的生产者
while (true)
{
collection.Add(GenerateData()); // 可能导致内存溢出
}
修正方案:
csharp复制// 添加背压控制
while (true)
{
if (!collection.TryAdd(GenerateData(), TimeSpan.FromMilliseconds(100)))
{
WaitForBackpressure();
}
}
错误配置:
csharp复制// 单消费者处理慢速IO
var collection = new BlockingCollection<WorkItem>(10000);
// 生产者快速添加...
修正方案:
csharp复制// 增加消费者数量
for (int i = 0; i < Environment.ProcessorCount * 2; i++)
{
StartConsumerThread();
}
优化技巧:
csharp复制// 预分配数组作为底层存储
var buffer = new TradeRecord[100000];
var queue = new ConcurrentQueue<TradeRecord>();
var collection = new BlockingCollection<TradeRecord>(
queue, boundedCapacity: 100000);
设计模式:
csharp复制var rawData = new BlockingCollection<RawData>(1000);
var stage1 = new BlockingCollection<ProcessedData>(1000);
var stage2 = new BlockingCollection<ResultData>(1000);
// 多阶段并行处理
Parallel.Invoke(
() => Stage1Processor(rawData, stage1),
() => Stage2Processor(stage1, stage2),
() => ResultWriter(stage2));