DEV Community

Cover image for Channel<T> in .NET: Asynchronous Processing, In-Memory Queues, and Backpressure Control
Pedro Luis Paredes Cabral
Pedro Luis Paredes Cabral

Posted on

Channel<T> in .NET: Asynchronous Processing, In-Memory Queues, and Backpressure Control

Hello Devs,

As .NET developers, we must learn to use Channels from the System.Threading library to build producer-consumer flows, job queues, and background processing in .NET applications.

When an application needs to receive work quickly and process it asynchronously, one of the first solutions that usually comes to mind is using a queue.

In some scenarios, we might need to:

  • Process tasks in the background.
  • Decouple the receipt of a request from its processing.
  • Limit the number of pending items.
  • Distribute work among several consumers.
  • Apply backpressure when producers generate work faster than it can be processed.

Channel is an asynchronous synchronization structure designed to securely transfer data between one or more producers and consumers.

In simple terms, it functions as an asynchronous queue optimized for concurrent scenarios.

How does Channel work?

A channel is mainly divided into two components:

ChannelWriter<T>
ChannelReader<T>
Enter fullscreen mode Exit fullscreen mode

The writer introduces elementos:

await channel.Writer.WriteAsync(item);
Enter fullscreen mode Exit fullscreen mode

The reader consumes the elements:

var item = await channel.Reader.ReadAsync();
Enter fullscreen mode Exit fullscreen mode

Example:

using System.Threading.Channels;

var channel = Channel.CreateUnbounded<int>();

var producer = ProduceAsync(channel.Writer);
var consumer = ConsumeAsync(channel.Reader);

await Task.WhenAll(producer, consumer);

static async Task ProduceAsync(ChannelWriter<int> writer)
{
    try
    {
        for (var number = 1; number <= 10; number++)
        {
            await writer.WriteAsync(number);

            Console.WriteLine($"Producido: {number}");

            await Task.Delay(100);
        }
    }
    finally
    {
        writer.Complete();
    }
}

static async Task ConsumeAsync(ChannelReader<int> reader)
{
    await foreach (var number in reader.ReadAllAsync())
    {
        Console.WriteLine($"Procesado: {number}");

        await Task.Delay(300);
    }
}

Enter fullscreen mode Exit fullscreen mode

Output:
Produced: 1
Processed: 1
Produced: 2
Produced: 3
Processed: 2
Produced: 4
Produced: 5
Produced: 6
Processed: 3
Produced: 7
Produced: 8
Processed: 4
Produced: 9
Produced: 10
Processed: 5
Processed: 6
Processed: 7
Processed: 8
Processed: 9
Processed: 10

This is a simple example of how we can use this powerful tool in applications that need to synchronize large amounts of data in a short time.

Top comments (0)