ConcurrentQueue
BlockingCollection
.Net
concurrency
threading

What are the differences between ConcurrentQueue and BlockingCollection in .Net?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

ConcurrentQueue<T> and BlockingCollection<T> are related, but they solve different concurrency problems. ConcurrentQueue<T> is a thread-safe FIFO container. BlockingCollection<T> is a producer-consumer coordination wrapper that can use a queue underneath but adds blocking, bounding, and completion semantics. If you need a shared queue only, use ConcurrentQueue<T>. If you need producers and consumers to coordinate safely over time, BlockingCollection<T> is often the better tool.

What ConcurrentQueue<T> Gives You

ConcurrentQueue<T> is a lock-free, thread-safe queue optimized for high-throughput enqueue and dequeue operations.

csharp
1using System;
2using System.Collections.Concurrent;
3
4class Program
5{
6    static void Main()
7    {
8        var queue = new ConcurrentQueue<int>();
9        queue.Enqueue(1);
10        queue.Enqueue(2);
11
12        if (queue.TryDequeue(out int value))
13        {
14            Console.WriteLine(value);
15        }
16    }
17}

Its contract is simple:

  • multiple threads can enqueue safely
  • multiple threads can dequeue safely
  • operations do not block waiting for data
  • there is no built-in bounded capacity
  • there is no built-in completion signal for consumers

That makes it a good low-level container, but not a complete workflow tool.

What BlockingCollection<T> Adds

BlockingCollection<T> is a higher-level producer-consumer abstraction. By default it can wrap a ConcurrentQueue<T>, but it adds semantics that the queue itself does not have.

csharp
1using System;
2using System.Collections.Concurrent;
3
4class Program
5{
6    static void Main()
7    {
8        using var collection = new BlockingCollection<int>(boundedCapacity: 2);
9
10        collection.Add(10);
11        collection.Add(20);
12
13        Console.WriteLine(collection.Take());
14        Console.WriteLine(collection.Take());
15    }
16}

The extra features are the important part:

  • 'Take() can block until an item is available'
  • 'Add() can block when the collection is full'
  • capacity can be bounded
  • producers can call CompleteAdding()
  • consumers can iterate with GetConsumingEnumerable() until completion

These are coordination features, not just storage features.

Producer-Consumer Example

A complete example shows the difference more clearly:

csharp
1using System;
2using System.Collections.Concurrent;
3using System.Threading.Tasks;
4
5class Program
6{
7    static async Task Main()
8    {
9        using var collection = new BlockingCollection<int>(boundedCapacity: 5);
10
11        Task producer = Task.Run(() =>
12        {
13            for (int i = 0; i < 10; i++)
14            {
15                collection.Add(i);
16            }
17            collection.CompleteAdding();
18        });
19
20        Task consumer = Task.Run(() =>
21        {
22            foreach (int item in collection.GetConsumingEnumerable())
23            {
24                Console.WriteLine($"Consumed {item}");
25            }
26        });
27
28        await Task.WhenAll(producer, consumer);
29    }
30}

With ConcurrentQueue<T> alone, you would have to add your own wait handle, completion signal, and capacity control to build the same behavior.

When to Prefer ConcurrentQueue<T>

Choose ConcurrentQueue<T> when:

  • you just need a thread-safe queue
  • consumers can poll or are driven by another signaling mechanism
  • you want minimal abstraction and high throughput
  • capacity does not need to be bounded

It is especially reasonable inside a custom infrastructure component where you already manage wake-up and shutdown logic separately.

When to Prefer BlockingCollection<T>

Choose BlockingCollection<T> when:

  • you are implementing a producer-consumer pipeline
  • consumers should wait until work exists
  • producers should slow down when capacity is full
  • you need a clear completion mechanism

In other words, BlockingCollection<T> is the more complete workflow primitive.

The Relationship Between Them

BlockingCollection<T> often uses ConcurrentQueue<T> underneath. That means these types are not competitors in the strict sense.

A common pattern is:

  • storage strategy: ConcurrentQueue<T>
  • coordination wrapper: BlockingCollection<T>

So the real question is not which one is faster in isolation. The real question is whether your code needs coordination semantics or only concurrent storage.

Modern Note

For new async-heavy code, System.Threading.Channels is often a stronger alternative than BlockingCollection<T>, especially when you want asynchronous producers and consumers without blocking threads. But within the comparison asked here, the distinction remains the same: queue container versus producer-consumer abstraction.

Common Pitfalls

The biggest mistake is using ConcurrentQueue<T> alone for a producer-consumer pipeline and then reinventing blocking, completion, and backpressure badly.

Another mistake is using BlockingCollection<T> in code that is deeply async and then blocking threads unnecessarily. In those cases, channels may fit better.

Developers also often forget that BlockingCollection<T> is not tied to queues only. It is a wrapper abstraction that can use different underlying collections.

Finally, do not benchmark these types without considering the behavior you actually need. A faster raw queue is not useful if you still need to bolt on the missing coordination pieces.

Summary

  • 'ConcurrentQueue<T> is a thread-safe FIFO container.'
  • 'BlockingCollection<T> adds blocking, bounding, and completion behavior for producer-consumer workflows.'
  • Use ConcurrentQueue<T> when you need concurrent storage only.
  • Use BlockingCollection<T> when threads need coordinated work exchange.
  • The key difference is not queue versus queue, but container versus coordination abstraction.

Course illustration
Course illustration

All Rights Reserved.