This post is part of a series on Channel in C# .NET. Of course, it’s always better to start at Part 1, but you can skip anywhere you’d like using the links below.
Part 1 – Getting Started
Part 2 – Advanced Channels
Part 3 – Understanding Back Pressure
Up until this point, we have been using what’s called an “Unbounded” Channel. You’ll notice it when we create the channel, we do something like so :
var myChannel = Channel.CreateUnbounded<int>();
But actually, we can do something like :
var myChannel = Channel.CreateBounded<int>(1000);
This isn’t too dissimilar from creating another collection type such as a List or an Array that has a limited capacity. In our example, we’ve created a channel that will hold at most 1000 items. But why limit ourselves? Well.. That’s where Back Pressure comes in.
What Is Back Pressure?
Back Pressure in computing terms (Especially when it comes to messaging/queuing) is the idea that resources (Whether it be things like memory, ram, network capacity or for example an API rate limit on a required external API) are limited. And we should be able to apply “pressure” back up the chain to try and relieve some of that load. At the very least, let others know in the ecosystem that we are under load and we may take some time to process their requests.
Generally speaking, when we talk about back pressure with queues. Almost universally we are talking about a way to tell anyone trying to add more items in the queue that either they simply cannot enqueue any more items, or that they need to back off for a period of time. More rarely, we are talking about queues purely dropping messages once we reach a certain capacity. These cases are rare (Since generally you don’t want messages to simply die), but we do have the option.
So how does that work with .NET channels?
Back Pressure Options For Channels
We actually have a very simple way of adding back pressure when using Channels. The code looks like so :
var channelOptions = new BoundedChannelOptions(5) { FullMode = BoundedChannelFullMode.Wait }; var myChannel = Channel.CreateBounded<int>(channelOptions);
We can specify the following Full Modes :
Wait
Simply make the caller wait before turning on a WriteAsync() call.
DropNewest/DropOldest
Either drop the oldest or the newest items in the channel to make room for the item we want to add.
DropWrite
Simply dump the message that we were supposed to write.
There are also two extra pieces of code you should be aware of.
You can call WaitToWriteAsync() :
await myChannel.Writer.WaitToWriteAsync();
This let’s us “wait out” the bounded limits of the channel. e.g. While the channel is full, we can call this to simply wait until there is space. This means that even if there is a DropWrite FullMode turned on, we can limit the amount of messages we are dropping on the ground by simply waiting until there is capacity.
The other piece of code we should be aware of is :
var success = myChannel.Writer.TryWrite(i);
This allows us to try and write to the queue, and return whether we were successful or not. It’s important to note that this method is not async. Either we can write to the channel or not, there is no “Well.. You maybe could if you waited a bit longer”.
Found: https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/#comment-4241
Channel is like async BlockingCollection.
Somewhat true except a BlockingCollection has one method of back pressure (Wait), rather than dropping for instance. So it’s far less extensible.
So in the example where there is a lineup of customers and I want to open 2 checkouts in parallel. How would I do that?