Introduction
The BlockingCollection<T>
class in C# is part of the System.Collections.Concurrent
namespace. It provides thread-safe operations for adding and removing items, making it ideal for producer-consumer scenarios. The BlockingCollection<T>
can be used with any collection that implements IProducerConsumerCollection<T>
, such as ConcurrentQueue<T>
, ConcurrentStack<T>
, or ConcurrentBag<T>
.
Key Features of BlockingCollection
- Thread-Safe: Supports concurrent add and remove operations.
- Bounded and Unbounded Collections: Can be configured to have a bounded (fixed) capacity or to be unbounded.
- Blocking Operations: Provides blocking and bounded capabilities for handling producers and consumers efficiently.
- Supports Multiple Underlying Collections: Can work with different underlying concurrent collections.
Creating a BlockingCollection
Declaration and Initialization
You can declare and initialize a BlockingCollection<T>
in several ways:
Example
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace BlockingCollectionExample
{
class Program
{
static void Main(string[] args)
{
// Creating an unbounded BlockingCollection
BlockingCollection<int> blockingCollection = new BlockingCollection<int>();
// Creating a bounded BlockingCollection with a maximum capacity of 5
BlockingCollection<int> boundedCollection = new BlockingCollection<int>(5);
// Using a ConcurrentQueue as the underlying collection
BlockingCollection<int> queueCollection = new BlockingCollection<int>(new ConcurrentQueue<int>());
// Using a ConcurrentStack as the underlying collection
BlockingCollection<int> stackCollection = new BlockingCollection<int>(new ConcurrentStack<int>());
}
}
}
Common Operations on BlockingCollection
Adding Elements
- Add: Adds an element to the
BlockingCollection
. Blocks if the collection is bounded and full.
blockingCollection.Add(1);
- TryAdd: Tries to add an element to the
BlockingCollection
within an optional time-out period.
bool success = blockingCollection.TryAdd(2, TimeSpan.FromSeconds(1)); // Waits up to 1 second
Removing Elements
- Take: Removes and returns the element from the
BlockingCollection
. Blocks if the collection is empty.
int item = blockingCollection.Take();
- TryTake: Tries to remove and return an element from the
BlockingCollection
within an optional time-out period.
bool success = blockingCollection.TryTake(out int item, TimeSpan.FromSeconds(1)); // Waits up to 1 second
Checking the Collection Size
- Count: Gets the number of elements in the
BlockingCollection
.
int count = blockingCollection.Count;
- BoundedCapacity: Gets the bounded capacity of the
BlockingCollection
.
int capacity = boundedCollection.BoundedCapacity;
Complete Adding
- CompleteAdding: Marks the
BlockingCollection
as not accepting any more additions.
blockingCollection.CompleteAdding();
IsAddingCompleted
- IsAddingCompleted: Checks if the
BlockingCollection
has been marked as complete for adding.
bool isAddingCompleted = blockingCollection.IsAddingCompleted;
IsCompleted
- IsCompleted: Checks if the
BlockingCollection
has been marked as complete for adding and is empty.
bool isCompleted = blockingCollection.IsCompleted;
Practical Example
Let’s create a practical example where we use a BlockingCollection<T>
to manage a producer-consumer scenario. In this example, multiple producer tasks add items to the collection, and multiple consumer tasks take items from the collection.
Example
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ProducerConsumerExample
{
class Program
{
static void Main(string[] args)
{
BlockingCollection<int> blockingCollection = new BlockingCollection<int>(5);
Task producer1 = Task.Run(() => ProduceItems(blockingCollection, 1));
Task producer2 = Task.Run(() => ProduceItems(blockingCollection, 2));
Task consumer1 = Task.Run(() => ConsumeItems(blockingCollection, 1));
Task consumer2 = Task.Run(() => ConsumeItems(blockingCollection, 2));
Task.WaitAll(producer1, producer2);
blockingCollection.CompleteAdding(); // Signal that no more items will be added
Task.WaitAll(consumer1, consumer2);
Console.WriteLine("All tasks completed.");
}
static void ProduceItems(BlockingCollection<int> collection, int producerId)
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"Producer {producerId} added item {i}");
Task.Delay(100).Wait(); // Simulate some work
}
}
static void ConsumeItems(BlockingCollection<int> collection, int consumerId)
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Consumer {consumerId} took item {item}");
Task.Delay(150).Wait(); // Simulate some work
}
}
}
}
Output
Producer 1 added item 0
Producer 2 added item 0
Producer 1 added item 1
Producer 2 added item 1
Consumer 1 took item 0
Consumer 2 took item 1
Producer 1 added item 2
Consumer 1 took item 2
Producer 2 added item 2
Producer 1 added item 3
Consumer 2 took item 3
...
All tasks completed.
Conclusion
The BlockingCollection<T>
class in C# provides a powerful way to manage collections of items in a thread-safe manner, especially for producer-consumer scenarios. It supports blocking and bounding capabilities, ensuring efficient handling of concurrent operations. Understanding how to use BlockingCollection<T>
effectively can help you manage collections of data in multi-threaded applications.