I´m trying to understand the purpose of BlockingCollection in the context of the new Parallel Stacks on .NET 4.
The MSDN documentation says:
BlockingCollection is used as a wrapper for an IProducerConsumerCollection instance, allowing removal attempts from the collection to block until data is available to be removed. Similarly, a BlockingCollection can be created to enforce an upper-bound on the number of data elements allowed in the IProducerConsumerCollection; addition attempts to the collection may then block until space is available to store the added items.
However when I look at the implementation of some IProducerConsumerCollection, like ConcurrentQueue I see that they provide a lock free, thread safe, implementations. So why is needed the lock mechanism that BlockingCollection provides? All the examples in the MSDN show using those collections via BlockingCollection wrapper, what are the troubles of using those collections directly? What benefit produces using BlockingCollection?
Blocking until the operation can be performed is a convenience if you have nothing else to do anyway (or rather: cannot proceed until the operation has been performed).
If you have a non-blocking queue from which you want to read data, and there is no data at the moment, you have to periodically poll it, or wait on some semaphore, until there is data. If the queue blocks, that is already done automatically.
Similarly, if you try to add to a non-blocking queue that is full, the operation will just fail, and then you have to figure out what to do. The blocking queue will just wait until there is space.
If you have something clever to do instead of waiting (such as checking another queue for data, or raising a QueueTooFullException) then you want the non-blocking queue, but often that is not the case.
Often, there is a way to specify a timeout on blocking queues.
The purpose of locking is the locking itself. You can have several threads read from the collection, and if there is no data available the thread will just stay locked until new data arrives.
Also, with the ability to set a size limit, you can let the producer thread that is filling the collection just feed as much as it can into it. When the collection reaches the limit, the thread will just lock until the consumer threads have made space for the data.
This way you can use the collection to throttle the throughput of data, without doing any checking yourself. Your threads just read and write all they can, and the collection takes care of keeping the threads working or sleeping as needed.
It's one of those things that's much easier to understand once you do it.
For producer consumer, let's have two objects, Producer and Consumer. They both share a queue they're given when constructed, so they can write between it.
Adding in a producer consumer is pretty familiar, just with the CompleteAdding a little different:
public class Producer{
private BlockingCollection<string> _queue;
public Producer(BlockingCollection<string> queue){_queue = queue;}
//a method to do something
public MakeStuff()
{
for(var i=0;i<Int.MaxValue;i++)
{
_queue.Add("a string!");
}
_queue.CompleteAdding();
}
}
The consumer doesn't seem to make sense - until you realize that the foreach will not stop looping UNTIL the queue has completed adding. Until then, if there's no items, it will just go back to sleep. And since it's the same instance of the collection in the producer and consumer, you can have the consumer ONLY taking up cycles when there's actually things to do, and not have to worry about stopping it, restarting it, etc.
public class Consumer()
{
private BlockingCollection<string> _queue;
public Consumer(BlockingCollection<string> queue)
{
_queue = queue;
}
public void WriteStuffToFile()
{
//we'll hold until our queue is done. If we get stuff in the queue, we'll start processing it then
foreach(var s in _queue.GetConsumingEnumerable())
{
WriteToFile(s);
}
}
}
So you wire them together by using the collection.
var queue = new BlockingCollection<string>();
var producer = new Producer(queue);
var consumer = new Consumer(queue);
producer.MakeStuff();
consumer.WriteStuffToFile();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With