BlockingCollection<T>
是.NET Framework中的一个线程安全集合,它提供了阻塞和限制功能。这意味着如果集合为空,尝试从集合中取出元素的操作将会阻塞,直到有元素可以取出为止;如果集合已达到设定的容量上限,尝试添加元素的操作也将会阻塞,直到集合中有空间为止。这使得BlockingCollection<T>
非常适合用于生产者-消费者场景。
BlockingCollection<T>
是 .NET Framework 中提供的一个集合类,专为并发场景设计,以支持多线程的生产者-消费者模式。这个集合的特点使其成为在多线程应用程序中进行数据共享和处理的理想选择。以下是 BlockingCollection<T>
的一些关键特点:
BlockingCollection<T>
内部采用适当的锁机制,确保在多线程环境下的操作(如添加或移除元素)是线程安全的。这意味着开发者无需担心多线程并发访问和修改集合所带来的竞争条件或数据不一致的问题。
BlockingCollection<T>
提供了阻塞和非阻塞的数据访问方法。例如,Add
方法用于添加元素,而 Take
方法在尝试取出元素时,如果集合为空,则会阻塞调用线程直到集合中有元素可取。此外,还提供了 TryAdd
和 TryTake
方法,这些方法在无法立即执行时不会阻塞调用线程,而是返回一个布尔值指示操作是否成功。
BlockingCollection<T>
可以是有界的也可以是无界的。在创建集合时,可以指定一个容量上限来创建有界集合,这意味着当集合达到其容量上限时,尝试添加更多元素的操作将会阻塞,直到集合中有空间为止。如果不指定容量上限,则集合为无界的,理论上可以无限制地添加元素(受系统内存限制)。
虽然 BlockingCollection<T>
本身提供了一套丰富的API,但它实际上是一个包装器,可以包装不同的线程安全集合,如 ConcurrentQueue<T>
、ConcurrentStack<T>
和 ConcurrentBag<T>
。这意味着你可以根据需要选择不同的数据结构作为底层存储,以支持不同的使用场景。
BlockingCollection<T>
提供了 CompleteAdding
方法,允许生产者通知消费者不会再有更多的元素被添加到集合中。这对于确保消费者可以正确地完成处理并退出消费循环是非常有用的。
BlockingCollection<T>
支持多个生产者和多个消费者的场景,这使得它非常适合复杂的并行处理和数据流动任务。
下面是一个简单的生产者-消费者模式的示例,其中生产者将整数添加到BlockingCollection
中,消费者则从中取出这些整数。
C#using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
BlockingCollection<int> collection = new BlockingCollection<int>(boundedCapacity: 5);
// 生产者任务
var producer = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"生产: {i}");
}
collection.CompleteAdding();
});
// 消费者任务
var consumer = Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"消费: {item}");
}
});
Task.WaitAll(producer, consumer);
}
}
在这个示例中,我们创建了一个BlockingCollection<int>
,其容量限制为5。然后,我们启动了一个生产者任务,它向集合中添加整数,以及一个消费者任务,它从集合中取出并打印这些整数。当生产者完成添加操作后,我们调用CompleteAdding
方法来通知消费者不会有更多的项被添加到集合中。
下面是一个使用WinForms实现的生产者-消费者示例。在这个示例中,我们将创建一个简单的界面,其中包含用于启动和停止生产者和消费者任务的按钮,以及显示信息的文本框。
首先,创建一个新的WinForms应用程序,并添加以下控件:
Button
控件,分别命名为startButton
和stopButton
。TextBox
控件,命名为logTextBox
,将其Multiline
属性设置为True
以显示多行文本。然后,添加以下代码:
C#using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BlockingCollectionExample
{
public partial class MainForm : Form
{
private BlockingCollection<int> _collection = new BlockingCollection<int>(5);
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private Task _producerTask;
private Task _consumerTask;
public MainForm()
{
InitializeComponent();
}
private void startButton_Click(object sender, EventArgs e)
{
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken ct = _cancellationTokenSource.Token;
// 生产者任务
_producerTask = Task.Run(() =>
{
try
{
for (int i = 0; !ct.IsCancellationRequested; i++)
{
_collection.Add(i);
this.Invoke((Action)(() =>
{
logTextBox.AppendText($"生产: {i}\r\n");
}));
Thread.Sleep(500); // 模拟生产耗时
}
}
catch (OperationCanceledException)
{
_collection.CompleteAdding();
}
}, ct);
// 消费者任务
_consumerTask = Task.Run(() =>
{
try
{
foreach (var item in _collection.GetConsumingEnumerable(ct))
{
this.Invoke((Action)(() =>
{
logTextBox.AppendText($"消费: {item}\r\n");
}));
Thread.Sleep(1000); // 模拟消费耗时
}
}
catch (OperationCanceledException) { }
}, ct);
}
private void stopButton_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
try
{
Task.WaitAll(new[] { _producerTask, _consumerTask });
}
catch (AggregateException) { }
_collection = new BlockingCollection<int>(5); // 重置集合
}
}
}
在这个WinForm应用程序中,当用户点击startButton
时,将启动生产者和消费者任务。生产者将整数添加到BlockingCollection
中,而消费者则从中取出这些整数并显示在logTextBox
中。点击stopButton
将取消这些任务并重置集合,以便可以重新开始。
这个示例展示了如何在WinForms应用程序中使用BlockingCollection
来实现线程安全的生产者-消费者模式,同时也展示了如何使用CancellationToken
来优雅地取消任务。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!