Channel<T> provides an ordered, thread-safe queue for asynchronous communication between multiple producers and consumers. Writers add messages through the channel writer, while readers await values through the channel reader. A worker can run with Task.Run, reach await, and release its thread-pool thread instead of blocking it. Consumers use an asynchronous read loop and catch ChannelClosedException to detect completion. section 13.2.3
Listing 13.12 Async background processing with Channel<T>
var ch = Channel.CreateUnbounded<int>();
Task[] tasks = new Task[10];
for(int i=0; i<10;++i) #1
{
var threadNumber = i;
tasks[i] = Task.Run(async () =>
{
var rng = new Random((int)threadNumber);
int count = 0;
while (true)
{
try
{
var currentValue = await ch.Reader.ReadAsync(); #2
Console.WriteLine($"task {threadNumber} value {currentValue}");
Thread.Sleep(rng.Next(500));
count++;
}
catch(ChannelClosedException) #3
{
break;
}
}
Console.WriteLine($"task {threadNumber}, total {count} items");
});
}
for (int i = 0; i < 100; i++) #4
{
await ch.Writer.WriteAsync(i);
}
ch.Writer.Complete(); #5
Task.WaitAll(tasks); #6
A runnable pattern creates an unbounded integer channel, starts ten asynchronous consumers, writes 100 values, and reports each worker’s processed count. section 13.2.3
