A channel is a data structure that stores and produces data for consumers to process it. It works based on the producer-consumer approach. So let’s understand what is producer-consumer approach.
Producer-Consumer Approach
To understand this approach, let's imagine a post office. we will be dropping our letters/ Packages at a post office or post box, and each piece of mail will be added to aqua and stored in their warehouse; now, the employees will process the mail one by one and send it to their destinations. This is exactly the producer-consumer approach. The one who drops the mail is the producer. Postal Workers who process and send the mail to their destination are the consumers, and the mail queue is a buffer system. Like mail can be dropped at any point in time, a producer can queue the amount of work at any point in time, and like the postal workers, the consumer will pick it up from the queue and process it. The major advantages of this approach are
- Production & Consumption is decoupled: You can drop mail at any point in time, which will not impact the processing of the mail.
- Volume Management: During peak times, the volume of incoming mail can be very high, and this surge is maintained by the mail que, the postal workers ca process the mail at a manageable rate.
- Order & Organization: The que helps to process the mails in a sequence, this helps to reduce the risk of misplacement or errors.
Now let's go back to the Channel. A channel is a thread-safe data structure for passing data between producers and consumers and a Channel is one of the options provided by dotnet to implement a producer-consumer approach. It is safer and mainly targeted for communication between multiple threads. The channel mainly does the job of the post office, where it will store the mail from producers and then notify the postal workers. Now let’s see the channel in action.
A Channel will have a reader and writer. Based on the way you create the channel, the behavior of the reader and writer varies. There are two ways you can create a channel,
- Bounded channel: A bounded channel has a maximum capacity, once the maximum capacity is reached the channel asynchronously blocks the producer until space is available.
- Unbounded Channel: There is no limit for this channel, any number of readers or writers can use this channel concurrently.
Let us Go ahead and create a bounded channel.
The above code snippet will create a channel with a maximum limit of 50.
Other than channel capacity, a few options are available to configure the channel.
- FullMode: defines the operation that the writer has to perform when the channel is full.
- BoundedChannelFullMode.Wait –(Default Value): Writer will wait for the space to be available to write
- BoundedChannelFullMode.DropNewest: Remove the newest item in the channel to create room for the incoming item
- BoundedChannelFullMode.DropOldest: Remove the oldest item in a channel to create room for a new item
- BoundedChannelFullMode.DropWrite: Drops the writes until space is available in the channel
- SingleReader: True means the channel will guarantee only one read at a time from the channel
- SingleWriter: True means the channel will guarantee only one write at a time to the channel
Let’s modify our code a bit.
Now let's go ahead and start writing to the channel. There are multiple methods available to write to a channel.
- WriteAsync Method: Data will be written to the channel asynchronously.
- TryWrite: Attempts to write a specified item to channel if success returns true.
- WaitToWriteAsync: Returns a task that will be completed when space is available to write.
Now let’s take a look into the consumer side Let's start reading the post messages and process them. Consumer functionalities are exposed using the Reader. There are multiple ways to retrieve data from the channel using the reader.
- .ReadAllAsync: Returns all the data in the channel.
- TryRead: Returns true if data reading was successful.
- ReadAsync: Asynchronously reads data from the channel.
- WaitToReadAsync: Returns a task that will be complete once the data is available to read.
Now let's have a look into another use case where on a holiday post office should not accept or Process mail. Basically, we have to close the post office (writer). There are 2 methods available in c# to complete a writer.
- TryComplete: Attempt to mark the channel as complete.
- Complete: marks the channel as being complete.
Once we mark the channel as being complete, then no more writes are allowed to the channel.
I am attaching the source code with this blog, and also handling a few exceptions while writing to the channel which you can refer to in the same. Now let's cook up the entire bits and pieces in a simple way to build the flow.
Note. This is a simple code for understanding purposes. We can utilize the full functionality of Channels and Make it more interesting.