patterncsharpdotnetTip
Channel<T>: bounded producer/consumer queue with backpressure
Viewed 0 times
.NET 5+
Channel producer consumerSystem.Threading.ChannelsBoundedChannel backpressureReadAllAsync IAsyncEnumerableasync queue csharp
Problem
Producer/consumer patterns implemented with ConcurrentQueue<T> and manual polling burn CPU with busy-waits. BlockingCollection<T> blocks threads. There is no built-in async, cancellation-aware, bounded queue.
Solution
Use System.Threading.Channels for high-performance async producer/consumer:
// Create a bounded channel (backpressure: writer waits when full)
var channel = Channel.CreateBounded<WorkItem>(new BoundedChannelOptions(capacity: 100)
{
FullMode = BoundedChannelFullMode.Wait, // writer awaits when full
SingleReader = false,
SingleWriter = false
});
// Producer (e.g., HTTP endpoint)
app.MapPost("/enqueue", async (WorkItem item, CancellationToken ct) =>
{
await channel.Writer.WriteAsync(item, ct); // awaits if channel full
return Results.Accepted();
});
// Consumer (background service)
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var item in channel.Reader.ReadAllAsync(stoppingToken))
{
try { await ProcessAsync(item, stoppingToken); }
catch (Exception ex) { _logger.LogError(ex, "Failed item {Id}", item.Id); }
}
}
// Signal no more writes
channel.Writer.Complete();Why
Channel uses lock-free data structures and ValueTask for allocation-efficient async waits. ReadAllAsync wraps the reader in an IAsyncEnumerable, enabling await foreach with automatic cancellation token propagation.
Gotchas
- BoundedChannelFullMode.DropOldest silently drops items — only use when data loss is acceptable
- Channel.Writer.Complete(exception) marks the channel as faulted; readers will throw on completion
- Always await TryComplete() / Complete() during shutdown to allow consumers to drain remaining items before cancellation
Revisions (0)
No revisions yet.