Creating a blocking QueueT in .NET?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A blocking queue is a classic producer-consumer tool. Producers add items, consumers remove items, and either side waits when progress is temporarily impossible. In .NET, the standard building block for this pattern is usually BlockingCollection<T> rather than a custom queue with manual locks and condition variables.
Using the built-in collection keeps the code shorter, safer, and easier to stop cleanly when work is finished.
Why BlockingCollection<T> Fits The Pattern
BlockingCollection<T> wraps an IProducerConsumerCollection<T> and adds blocking and completion semantics. That means producers can block when a bounded queue is full, and consumers can block when no items are available yet.
The default underlying store is ConcurrentQueue<T>, so it behaves like a queue out of the box.
Basic Producer-Consumer Example
The example below creates a bounded blocking queue with one producer and one consumer:
When the queue reaches capacity, Add blocks until the consumer removes something. When the queue is empty, GetConsumingEnumerable waits until new data arrives or adding is completed.
Cancellation Support
In real services, blocking forever is rarely acceptable. You often want cancellation:
Cancellation tokens are especially useful when the queue is part of a hosted service that needs graceful shutdown behavior. They also make the queue easier to integrate with the rest of a Task-based application.
Why Not Build It From Scratch
You can implement a blocking queue manually with lock, Monitor.Wait, and Monitor.Pulse, but that is usually unnecessary. The custom version is easy to get wrong, especially around shutdown semantics, cancellation, fairness, or exception safety.
Unless you need a very specialized queue behavior, BlockingCollection<T> is the pragmatic choice.
When To Use Channels Instead
For newer asynchronous workflows, System.Threading.Channels can be an even better fit, especially when consumers are async methods. Channels are designed around asynchronous producers and consumers, whereas BlockingCollection<T> is oriented toward blocking thread-based coordination.
So the right answer depends on the style of your application. For classic multithreaded producer-consumer code, BlockingCollection<T> remains an excellent tool.
Common Pitfalls
The most common mistake is forgetting to call CompleteAdding(). Without it, consumers using GetConsumingEnumerable() may wait forever because they have no signal that production is finished.
Another pitfall is mixing blocking queue operations into an async codebase without thinking about thread usage. Blocking calls tie up threads. If the workflow is deeply asynchronous, channels may be more appropriate.
A third issue is writing a custom blocking queue before trying the standard library. Most homegrown versions duplicate features that .NET already provides and often handle shutdown less reliably.
Summary
- '
BlockingCollection<T>is the standard .NET tool for a blocking queue pattern.' - It supports bounded capacity, blocking producers and consumers, and graceful completion.
- '
GetConsumingEnumerable()is a simple way to consume until production is finished.' - Add cancellation when the queue participates in service shutdown or timeout logic.
- Prefer built-in concurrent primitives over hand-rolled locking code.

