.NET 中的 BlockingCollection 简介
BlockingCollection 是 System.Collections.Concurrent 命名空间下的一个类,顾名思义,与此命名空间下的任何其他集合一样,它也可以用于并发和多任务场景。 根据我的经验,很多开发者都熟悉 ConcurrentBag、CuncurrentDictionary、ConcurrentQueue 和 ConcurrentStack。但是很少有人知道 BlockingCollection 的威力和用途。
在继续之前,让我们看一下使用并发队列的经典示例。想象一下这个场景,我们有几个线程将用户的电子邮件添加到队列中,并且有一个线程从队列中读取电子邮件并向他们发送电子邮件。
class EmailService
{
private ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
public void AddEmail(string email)
{
_queue.Enqueue(email);
}
public void StartSendingEmail()
{
while (true)
{
bool isNotEmpty = _queue.TryDequeue(out string email);
if (isNotEmpty)
{
SendEmail(email);
}
else
{
Thread.Sleep(1000);
}
}
}
private void SendEmail(string email)
{
//Send email here
}
}
在前面的代码中,我们有一个 AddEmail 方法,不同的线程使用该方法将项目添加到队列中。在 StartSendingEmail 方法中,我们首先尝试从队列中挑选一封电子邮件并发送一封电子邮件。如果队列中没有电子邮件,我们将等待 1000 毫秒,然后再次尝试从列表中选择一个新项目(如果存在)。这种情况一直在继续。
我们在这里使用一种简单的轮询技术。这里的问题是如何计算出这 1000 毫秒。我们不知道何时可以将电子邮件添加到队列中以进行取件。 .NET Framework 中还有一些其他旧的线程类可以一起使用并解决这个问题,但在这里我们将利用 BlockingCollection。
BlockingCollection 实际上是实现了 IProducerConsumerCollection<T> 接口的并发集合的包装器。最著名的集合是 ConcurrentBag、ConcurrentQueue 和 ConcurrentStack。
以下代码是解决相同问题的类似解决方案,但这次使用 BlocingCollection。
class EmailService
{
private BlockingCollection<string> _collection = new BlockingCollection<string>();
public void AddEmail(string email)
{
_collection.Add(email);
}
public void StartSendingEmail()
{
while (true)
{
string email = _collection.Take();
SendEmail(email);
}
}
private void SendEmail(string email)
{
//Send email here
}
}
查看 StartSendingEmail 方法,看看它是如何被简化的。
BlocingCollection 提供了一个名为 Take 的方法。 此方法从集合中返回(移除)一个项目(如果存在),否则会阻塞线程,直到将来有新项目可用(这意味着稍后会将新电子邮件添加到集合中)。 所以我们不再需要暂停操作1秒再开始轮询,甚至不用关心集合是否为空。
还有一种方法可以通知 BlocingCollection 不会将新电子邮件添加到集合中。 这意味着如果 BlocingCollection 已经为空,则无需再等待新项目。 这可以通过调用 CompleteAdding 方法来完成。 调用此方法后,如果调用 Take 方法集合为空,则会抛出 InvalidOperationException。
让我们在 FinishSendingEmail 的代码中添加另一个功能。
public void FinishSendingEmail()
{
_collection.CompleteAdding();
}
public void StartSendingEmail()
{
while (true)
{
try
{
string email = _collection.Take();
SendEmail(email);
}
catch (InvalidOperationException)
{
// we are done!
return;
}
}
}
通过调用 FinishSendingEmail 方法,Take 方法会抛出 InvalidOperationException 异常(如果集合为空)。 我们只需要处理这个异常并退出循环。
现在你可能会问,从集合中拾取的项的顺序是什么?
在本文开头,我提到 BlockingCollection 是 IProducerConsumerCollection<T> 实现的包装器。 BlockingCollection 默认使用 ConcurrentQueue 作为底层数据源。 但是我们可以明确指定应该使用哪个数据源。 这意味着如果我们希望以 LIFO(后进先出)顺序获取项目(此处为电子邮件),只需在初始化时将 ConcurrentStack 的实例发送到集合即可:
BlockingCollection<string> _collection = new BlockingCollection<string>(new ConcurrentStack<string>());
在这里,我们讨论了 BlockingCollection 的一些基本特性,但是这个类仍然提供了很多。 例如,可以将 CancellationToken 作为参数发送到 Take 方法。
原文:https://weblogs.asp.net/morteza/an-introduction-to-blockingcollection
补充:
-
BlockingCollection
is a thread-safe collection class that provides the following features: -
An implementation of the Producer-Consumer pattern.
-
Concurrent adding and taking of items from multiple threads.
-
Optional maximum capacity.
-
Insertion and removal operations that block when collection is empty or full.
-
Insertion and removal "try" operations that do not block or that block up to a specified period of time.
-
Encapsulates any collection type that implements IProducerConsumerCollection
-
Cancellation with cancellation tokens.
-
Two kinds of enumeration with foreach (For Each in Visual Basic):
1. Read-only enumeration.
2. Enumeration that removes items as they are enumerated.

浙公网安备 33010602011771号