Producer/consumer in C# with Channel<T>: bounded channels, backpressure and parallel workers

C# Async & Multithreading November 13, 2026 csharp

Channel is async: no thread blocks while waiting, unlike BlockingCollection.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ======================================================================
// Q: How do you implement producer/consumer? Why Channel<T>?
//
// - Channel is async: no thread blocks while waiting, unlike BlockingCollection.
// - Bounded = backpressure, so memory doesn't fill up if the consumer is slow.
//   Unbounded only when the rate is under control.
// - It is in-process: if the process dies, the messages are lost. Anything that
//   must not be lost goes to an outbox + broker (Q30).
// ======================================================================

var channel = Channel.CreateBounded<OrderEvent>(new BoundedChannelOptions(1_000)
{
    FullMode = BoundedChannelFullMode.Wait     // backpressure: the producer waits when it is full
});

// Producer (e.g. an endpoint)
await channel.Writer.WriteAsync(evt, ct);

// Consumers: 4 workers reading in parallel (e.g. inside a BackgroundService)
var workers = Enumerable.Range(0, 4).Select(_ => Task.Run(async () =>
{
    await foreach (var e in channel.Reader.ReadAllAsync(ct))
        await HandleAsync(e, ct);
}));
await Task.WhenAll(workers);