Using C# Channels to write decoupled producers/consumers

Using C# Channels to write decoupled producers/consumers

The System.Threading.Channels namespace in C# provides a set of types for creating and working with channels. Channels are a mechanism for communication between threads, allowing one thread to send data to another thread. This is particularly useful in scenarios where you have multiple producer threads that generate data, and one or more consumer threads that process that data.

I recently used the classes in the System.Threading.Channels namespace in a project to decouple a bunch of OPC-UA servers from GRPC clients. In this project, the OPC-UA servers acted as producers, generating data that needed to be sent to the GRPC clients. To decouple these two components, I used channels to pass the data between the producer and consumer threads.

One way to implement a producer/consumer pattern using channels is to use the Channel<T> class, which is a thread-safe bounded buffer that can hold a fixed number of items of type T. Here's an example of how you might create a channel and use it to pass data between producer and consumer threads:

using System
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;


namespace ConsoleApplication
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // Create a channel with a capacity of 100 items
            var channel = Channel.CreateBounded<string>(100);


            // Start a producer task that will write to the channel
            var producer = Task.Run(() => Produce(channel.Writer));


            // Start a consumer task that will read from the channel
            var consumer = Task.Run(() => Consume(channel.Reader));


            // Wait for both tasks to complete
            await Task.WhenAll(producer, consumer);
        }


        static async Task Produce(ChannelWriter<string> writer)
        {
            for (int i = 0; i < 1000; i++)
            {
                // Write a message to the channel
                await writer.WriteAsync($"Message {i}");
            }


            // Signal that we're done producing
            writer.Complete();
        }


        static async Task Consume(ChannelReader<string> reader)
        {
            while (await reader.WaitToReadAsync())
            {
                // Read a message from the channel
                while (reader.TryRead(out var message))
                {
                    Console.WriteLine(message);
                }
            }
        }
    }
};        

In this example, the Produce method writes 1000 messages to the channel, and the Consume method reads them from the channel and writes them to the console. The Channel<T>.CreateBounded method creates a channel with a fixed capacity of 100 items, which means that if the producer tries to write more than 100 items to the channel, it will block until there is space available in the channel.

The ChannelWriter<T>.WriteAsync and ChannelReader<T>.ReadAsync methods can be used to send and receive items asynchronously, and the ChannelWriter<T>.Complete method can be used to signal that the producer is done producing items. The ChannelReader<T>.WaitToReadAsync method can be used to wait until there is an item available to be read, and the ChannelReader<T>.TryRead method can be used to try to read an item without blocking.

There are many other ways to use channels beyond what is shown in the example above. For instance, you can use the Channel<T>.CreateUnbounded method to create an unbounded channel, or the Channel<T>.CreateBatch method to write and read multiple items at a time. You can also use the Channel<T>.Reader.TryRead method to try to read an item without blocking, or the Channel<T>.Reader.Completion property to get a Task that will complete when the channel is closed.

Overall, the System.Threading.Channels namespace provides a flexible and efficient way to communicate between threads in C#, and I highly recommend it for any multithreaded application.

Jonas Jakobsson

Producer of weirdly formatted text

2y

very cool, have never used that one!

Jasmin Akther Suma

Lead Software Engineer at DataPath Ltd. | Full Stack .NET Developer | .NET Core | C# | AngularJS | Angular | TypeScript | JavaScript | MSSQL | Avid Book Reader

2y

Written Well So Easily Understood. Thanks :) 

Brian Boyd

Founder SaaS • Architecting Intelligent Systems | AI Strategy • Semantic Graphs • High-Performance Engineering

2y

Cleanly and clearly written, thanks!

To view or add a comment, sign in

More articles by Sebastiano Gazzola

Insights from the community

Others also viewed

Explore topics