The BCL contains helpful classes to build your producer-consumer scenarios. We want to have a thread or several that produces data and one or several threads
that consume that data. In this article I will show a simple single producer, single consumer scenario. BlockingCollection contains the necessary logic to support this. Consider the following code:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace BlockingCollection
{
class Program
{
private static readonly BlockingCollection<int> _blockingQueue = new BlockingCollection<int>(boundedCapacity: 1);
static void Main(string[] args)
{
Task.Run(() =>
{
foreach (var item in _blockingQueue.GetConsumingEnumerable())
{
Console.WriteLine("Hey I got this item: " + item + "!");
Thread.Sleep(1000);
}
});
int[] nums = { 1, 3, 8, 11, 94, 28 };
foreach (var n in nums)
_blockingQueue.Add(n);
_blockingQueue.CompleteAdding();
Console.WriteLine("Hit the any key!");
Console.ReadKey();
}
}
}
We create a blocking collection with a bound capacity of one item. This will only accept a single item at a time. This means that our
second call to the .Add() method will actually block. We also use the .CompleteAdding() method to signal that we are finished filling up values. It is also important to start up our consumer before the blocking call. I use Task.Run()here to start up a new thread. We will inside that thread Call the .GetConsumingEnumerable() to set up our consumer. Note that I also use Thread.Sleep to make the consumer wait a bit, this is only for testing and normally you would of course not wait here. Note also that you could spawn multiple consumers with Task.Run
or similar to support 1 producer, multiple consumers scenarios. For multiple producers you could use a single shared blocking collection or possibly multiple blocking collections. BlockingCollection is very flexible. It is important that you call .CompleteAdding() on your producer side to allow the consumers to finish also their execution.
What if you want to not have a FIFO ordering in your Producer-Consumer scenario, but say a LIFO scenario? As you probably can see,
if you instantiate the BlockingCollection, the constructor takes overloads to somethhing called an IProducerConsumerCollection Collection.
So we just adjust our instantiation like the following (note that I had to remove the boundedCapacity here set to 1 to make it work!):
private static readonly BlockingCollection<int> _blockingQueue = new BlockingCollection<int>(
new ConcurrentStack<int>());
So now we get our new output:
Hit the any key!
Hey I got this item: 28!
Hey I got this item: 94!
Hey I got this item: 11!
Hey I got this item: 8!
Hey I got this item: 3!
Hey I got this item: 1!
The BCL contains several interesting classes in the System.Collections.Concurrent namespace. Happy .NET coding!