Producer Consumer Pattern In C#

Introduction

 
In today’s article we will look at the producer consumer pattern in C# and how we can implement this pattern using the System Threading Channels data structure. We will look at the advantages of using this data structure and see an example of it in action.

The Producer Consumer Pattern

 
The Producer Consumer pattern is where a producer generates some messages or data as we may call it and various consumers can read that data and work on it. The main advantage of this pattern is that the producer and consumer are not causally linked in any way. Hence, we can say this is a disconnected pattern. The System Threading Channels data structure can be used to achieve this pattern. It is easy to use and is thread safe as I will demonstrate in the below example.
 
Example of using the pattern
 
Let us create a console application in Visual Studio 2019 (Community edition) as below,
 
Producer Consumer Pattern In C#
 
Producer Consumer Pattern In C#
 
Producer Consumer Pattern In C#
 
We then add the below code,
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Threading.Channels;  
  4. using System.Threading.Tasks;  
  5. namespace Console App ProducerConsumerPattern {  
  6.     class Program {  
  7.         static void Main(string[] args) {  
  8.             var pc = new ProducerConsumer();  
  9.             pc.StartChannel();  
  10.             Console.ReadKey();  
  11.         }  
  12.     }  
  13.     public class ProducerConsumer {  
  14.         static int messageLimit = 5;  
  15.         Channel < string > channel = Channel.CreateBounded < string > (messageLimit);  
  16.         public void StartChannel() {  
  17.             List < string > names = new List < string > ();  
  18.             names.Add("John Smith");  
  19.             names.Add("Jane Smith");  
  20.             names.Add("John Doe");  
  21.             names.Add("Jane Doe");  
  22.             Task producer = Task.Factory.StartNew(() => {  
  23.                 foreach(var name in names) {  
  24.                     channel.Writer.TryWrite(name);  
  25.                 }  
  26.                 channel.Writer.Complete();  
  27.             });  
  28.             Task[] consumer = new Task[2];  
  29.             for (inti = 0; i < consumer.Length; i++) {  
  30.                 consumer[i] = Task.Factory.StartNew(async () => {  
  31.                     while (awaitchannel.Reader.WaitToReadAsync()) {  
  32.                         if (channel.Reader.TryRead(out  
  33.                                 var data)) {  
  34.                             Console.WriteLine($ " Data read from Consumer No.{Task.CurrentId} is {data}");  
  35.                         }  
  36.                     }  
  37.                 });  
  38.             }  
  39.             producer.Wait();  
  40.             Task.WaitAll(consumer);  
  41.         }  
  42.     }  
  43. }  
In the above example, we create a channel. There are two types of channels, bound and un-bound. Here we are using a bound channel which gives us more control over the channel like setting the maximum number of messages that the channel can carry. This is important in a scenario where we do not want to overload the channel with producer messages to a point that they cannot be handled by the consumers.
 
First, we create a simple generic list of strings. This will be the messages that will be sent on the channel by the producer to be read by the consumers. Next, we create a task for the producer which reads each string from the list and writes it to the channel. Next, we create two tasks for the consumers, which will read the messages (strings) from the channel and write them to the console. This is a simple example for demonstration purposes only. In a real-life scenario, we would probably pass some concrete object from the producer and in the consumer, we would process that object.
 
If we run the application, we get the below,
 
Producer Consumer Pattern In C#

Here, you can see that three items (strings) were processed by one of the consumer tasks and the remaining one by the other consumer task.
 

Summary

 
In this article, we looked at what the producer consumer pattern is in general. Then we saw how we can implement this pattern using the System Threading Channels data structure. We looked at an example of implementing the producer consumer pattern using a bound channel and the results we would get. Happy Coding!