Joydip Kanjilal
Contributor

How to use System.Threading.Channels in .NET Core

how-to
Oct 14, 20195 mins
C#Microsoft .NETSoftware Development

Take advantage of channels to implement the fast, efficient, asynchronous producer-consumer pattern in .NET Core.

thread weave spools connected rotate spin
Credit: Thinkstock

The System.Threading.Channels namespace contains types that you can use to implement a producer-consumer scenario, which speeds up processing by allowing producers and consumers to perform their tasks concurrently. This namespace contains a set of synchronization types that can be used for asynchronous data exchange between producers and consumers.

This article discusses how we can work with the System.Threading.Channels library in .NET Core.

Dataflow blocks vs. channels 

The System.Threading.Tasks.Dataflow library encapsulates both storage and processing, and it is focused primarily on pipelining. By contrast, the System.Threading.Tasks.Channels library is focused primarily on storage. Channels are much faster than Dataflow blocks but they are specific to producer-consumer scenarios. That means they don’t support some of the control flow features that you get with Dataflow blocks.

Why use System.Threading.Channels?

You can take advantage of channels to decouple producers from consumers in a publish-and-subscribe scenario. Producers and consumers not only improve performance by working in parallel, but you can create more producers or consumers should one of those tasks begin to outstrip the other. In other words, the producer-consumer pattern helps to increase the application’s throughput (a measure that denotes the amount of work done in a unit of time).

To work with the code examples illustrated in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here

Create a .NET Core Console App project in Visual Studio

First off, let’s create a .NET Core project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core Console App.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of the templates displayed.
  4. Click Next
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Create.

This will create a new .NET Core Console App project in Visual Studio 2019. We’ll use this project to illustrate the use of System.Threading.Channels in the subsequent sections of this article.

Install theSystem.Threading.Channels NuGet package

Now that we have created a .NET Core Console App in Visual Studio, the next thing you should do is install the necessary NuGet package. The package you need to install is System.Threading.Channels. You can install this package from the NuGet Package Manager inside the Visual Studio 2019 IDE. Alternatively, you can enter the following command to install this package via the .NET CLI.

dotnet add package System.Threading.Channels

Create a channel in .NET Core

Essentially, you can have two different types of channels. These include a bounded channel with a finite capacity and an unbounded channel with unlimited capacity. You can create either type of channel using the Channel static class that is available as part of the System.Threading.Channels namespace.

The Channel class provides the following two factory methods that can be used to create the two kinds of channels.

  • CreateBounded is used to create a channel that holds a finite number of messages.
  • CreateUnbounded is used to create a channel with unlimited capacity, i.e., a channel that (theoretically) holds an infinite number of messages.

The following code snippet illustrates how an unbounded channel can be created. Note that this channel can hold string objects only.

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

Bounded channel options include a FullMode property that is used to denote the behavior of the channel during a write operation when the channel is full. Bounded channels can make use of any of the following FullMode behaviors:

  • Wait
  • DropWrite
  • DropNewest
  • DropOldest

The following code snippet illustrates how you can create a bounded channel and set the FullMode property.

Channel.CreateBounded<string>(new BoundedChannelOptions(1000)
 {
     FullMode = BoundedChannelFullMode.Wait
 });

Write data to a channel in .NET Core

To write data to a channel, you can use the WriteAsync method as shown in the code snippet given below.

await channel.Writer.WriteAsync("Hello World!");

Read data from a channel in .NET Core

To read data from a channel you can leverage the ReadAsync method as shown in the code snippet given below.

while (await reader.WaitToReadAsync())
  {
      if (reader.TryRead(out var message))
         {
            Console.WriteLine(message);
         }
  }  

System.Threading.Channels example

Here is the complete code listing that demonstrates writing and reading data to and from a channel.

class Program
    {
        static async Task Main(string[] args)
        {
            await SingleProducerSingleConsumer();
            Console.ReadKey();
        }
        public static async Task SingleProducerSingleConsumer()
        {
            var channel = Channel.CreateUnbounded<int>();
            var reader = channel.Reader;
            for(int i = 0; i < 10; i++)
            {
                await channel.Writer.WriteAsync(i + 1);
            }           
            while (await reader.WaitToReadAsync())
            {
                if (reader.TryRead(out var number))
                {
                    Console.WriteLine(number);
                }
            }          
        }
    }

When executed, the above program will display the numbers 1 to 10 sequentially at the console window.

There are several ways of implementing a producer-consumer pattern, such as by using BlockingCollection or TPL Dataflow. However, channels are much faster and more efficient than either of these. I’ll discuss channels in further detail in future posts. Until then, you can learn more about the System.Threading.Channels namespace from Microsoft’s online documentation.  

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author