Reading — step 1 of 5
Learn
~1 min readMemory and Performance
System.Threading.Channels provides high-performance producer/consumer queues — replacing many uses of BlockingCollection<T>.
using System.Threading.Channels;
var channel = Channel.CreateUnbounded<int>();
// Producer:
await Task.Run(async () => {
for (int i = 0; i < 10; i++) {
await channel.Writer.WriteAsync(i * i);
}
channel.Writer.Complete(); // no more items
});
// Consumer:
await foreach (var value in channel.Reader.ReadAllAsync()) {
Console.WriteLine(value);
}
Channel types:
- Unbounded — backed by ConcurrentQueue, no size limit (memory only). Use cautiously.
- Bounded — limited to N items, with a strategy when full:
BoundedChannelFullMode.Wait— producer blocks (default)DropOldest— discards old items to make roomDropNewest— discards what was about to be addedDropWrite— silently fails the write
var bounded = Channel.CreateBounded<Work>(new BoundedChannelOptions(100) {
FullMode = BoundedChannelFullMode.Wait
});
SingleReader / SingleWriter — performance hints. If you guarantee only one reader (or writer) thread, the channel uses a faster code path.
Benefits over BlockingCollection:
- Async-native:
await ReadAsync()doesn't block a thread - No locks under contention
- Backpressure built in (bounded)
Pattern: pipeline of stages:
var parsed = Channel.CreateBounded<ParseResult>(100);
var processed = Channel.CreateBounded<ProcessedItem>(100);
// Stage 1: read input -> parsed
var parser = Task.Run(async () => {
await foreach (var line in ReadLinesAsync()) {
await parsed.Writer.WriteAsync(Parse(line));
}
parsed.Writer.Complete();
});
// Stage 2: parsed -> processed
var worker = Task.Run(async () => {
await foreach (var p in parsed.Reader.ReadAllAsync()) {
await processed.Writer.WriteAsync(Process(p));
}
processed.Writer.Complete();
});
Classic dataflow pipeline — and it composes naturally with await foreach.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…