C# BlockingCollection

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top