BlockingCollection实现生产者消费者模式
功能特性
BlockingCollection 是一个线程安全集合类,其内封装实现 IProducerConsumerCollection 的各种集合类型,如 ConcurrentQueue、ConcurrentStack 等。主要有以下特性:
- 可以快速实现生产者-消费者模式;
- 支持多个生产者(Producer)并发添加元素,多个消费者(Consumer)并发取出元素;
- 支持设置最大容量,设置最大容量后,生产者会在集合满时阻塞,直到有空间可用
- 当集合为空时,消费者可以阻塞,等待新元素加入;
- 当集合满时,生产者可以等待空间释放(可选)
- 使用GetConsumingEnumerable()返回一个可枚举对象,用于在消费者中遍历集合中的元素。它是一个无限枚举器,只有当集合为空 且 CompleteAdding() 被调用 后,才会退出循环,否则将会阻塞等待;
- 使用取消标记执行取消操作。
使用场景
| 场景 | 描述 |
|---|---|
| 多线程任务调度 | 多个后台线程从集合中获取任务执行 |
| 队列式处理 | 如日志处理、消息队列等 |
| 批量数据处理 | 支持多个生产者并发写入,多个消费者并发读取 |
| 异步流处理 | 结合 GetConsumingEnumerable() 实现持续消费 |
常用方法
| 功能 | 方法 |
|---|---|
| 添加元素(生产者方法) | Add(item) / TryAdd(…) |
| 获取元素(消费者方法) | Take() / TryTake(…) |
| 遍历消费(遍历消费) | GetConsumingEnumerable() |
| 标记完成 | CompleteAdding() |
| 是否已完成 | IsCompleted |
| 集合是否为空 | IsEmpty |
| 最大容量 | 通过构造函数传参控制 |
构造方式
// 使用默认内部队列(ConcurrentQueue<T>)
BlockingCollection<string> collection = new BlockingCollection<string>();
// 指定最大容量(有界集合)
BlockingCollection<string> boundedCollection = new BlockingCollection<string>(new ConcurrentQueue<string>(), 10);
// 使用其他集合类型(例如 ConcurrentStack)
BlockingCollection<int> stackBasedCollection = new BlockingCollection<int>(new ConcurrentStack<int>());
生产者消费者模式示例
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var collection = new BlockingCollection<string>();
// 消费者任务
Task consumerTask = Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"消费: {item}");
}
Console.WriteLine("消费结束");
});
// 生产者任务
Task producerTask = Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
collection.Add($"Item{i}");
Console.WriteLine($"生产: Item{i}");
Task.Delay(500).Wait();
}
collection.CompleteAdding(); // 完成生产
});
Task.WaitAll(consumerTask, producerTask);
Console.WriteLine("程序结束");
}
}
说明:只有调用collection.CompleteAdding(),且collection为空时,consumerTask才可能终止;否则,因为collection.GetConsumingEnumerable()的无限迭代,consumerTask将无法终止。如果某线程确定需要持续后台运行,则可不调用collection.CompleteAdding()。
消费者退出的两种方式
-
如上代码,在生产者完成添加后,主动调用 CompleteAdding(),当消费者消费所有内容后,则会自动退出。
-
结合 CancellationToken 来强制退出循环,适用于需要提前终止的情况
var cts = new CancellationTokenSource();
Task consumerTask = Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable(cts.Token))
{
Console.WriteLine($"消费: {item}");
}
Console.WriteLine("消费者已退出");
});
在之后,调用cts.Cancel()主动取消消费者。此时会抛出 OperationCanceledException,可以捕获它来更加灵活地控制退出。
1547

被折叠的 条评论
为什么被折叠?



