I’ve been getting familiar with the new Channel types introduced in.NET Core. I think I heard about it when it was first released, but there were very, very few articles about it, and I couldn’t understand how they were different from other queues.
After using them for a while, I finally saw their appeal and true power. Most notable are the large asynchronous background operations, which almost require two-way communication to synchronize what they are doing. This is a bit of a mouthful, but hopefully by the end of this series you’ll have a clear idea of when to use a Channel and when to use something more basic like Queue.
What is a Channel?
At its core, a Channel is essentially a new collection type in.NET that is very similar to the existing Queue type, but with additional benefits. The problem I discovered when really trying to explore this topic is that many of the existing external queue technologies (IBM MQ, Rabbit MQ, etc.) have the concept of “channels,” which range from completely abstract thought processes to the actual physical types in the system.
Now I could be dead wrong, but if you think of a Channel in.NET as a queue that allows you to wait for new messages and tells producers to keep the queue bigger and bigger and consumers can’t keep up, I think it’s hard to go wrong.
I mentioned a key word here, producer/consumer. You may also have heard of Pub/Sub. But they are not interchangeable.
Pub/Sub describes someone Posting information that one or more “subscribers” listen to and respond to. There is no load balancing because when subscribers are added, they essentially get a copy of the same message as everyone else.
In chart form, Pub/Sub looks something like this:
Producer/consumer describes the behavior of a producer publishing a message, and one or more consumers can act on the message, but each message is read only once. It will not be distributed to every subscriber.
Of course, in chart form:
Another way to think about producers/consumers is to imagine you go to the supermarket to check out. When customers want to check out, the lines get longer and you can simply open more checkout counters to deal with those customers. This little thought process is actually important, because what if you can’t open more checkout counters? Should the lines get longer? What if the cashier operator is sitting there, but there are no customers? Should they pack up and go home the same day, or should they be told to sit back and wait for the guests to arrive.
This is often referred to as the producer-consumer problem, and it’s the problem Channel is trying to solve.
Basic Channel example
Everything related to Channel in the System. The Threading. Channels. In later versions, this seems to be with the standard. Net Core project bundled together, but if not, there is a nuget package: www.nuget.org/packages/Sy…
An extremely simple example of a Channel looks like this:
static async Task Main(string[] args) { var myChannel = Channel.CreateUnbounded(); for (int i = 0; i < 10; i++) { await myChannel.Writer.WriteAsync(i); } while (true) { var item = await myChannel.Reader.ReadAsync(); Console.WriteLine(item); }}Copy the code
There’s not much to talk about here. We created an “infinite” channel (which means it can hold an infinite number of items, but more on that later in this series). We write 10 items, we read 10 items, and in that respect, it’s not that different from any other queue we’ve seen in.NET.
Channels are thread-safe
Yes, channels are thread-safe. This means that multiple threads can read and write to the same channel without problems. If we look at the Channel source here, we can see that it is thread-safe because it uses a combination of locks and internal “queues” to synchronize reads/writers, one after the other.
In fact, the intended use case for Channel is a multithreaded scenario. For example, if we use the basic code above, there is actually some overhead in maintaining thread-safety when we don’t really need it. So in that case, we might be better off just using Queue. But what about this code?
static async Task Main(string[] args) { var myChannel = Channel.CreateUnbounded(); _ = Task.Factory.StartNew(async () => { for (int i = 0; i < 10; i++) { await myChannel.Writer.WriteAsync(i); await Task.Delay(1000); }}); while (true) { var item = await myChannel.Reader.ReadAsync(); Console.WriteLine(item); }}Copy the code
Here, we have a single thread writing messages, and our main thread reading messages. The interesting thing you’ll notice is that we’ve added a delay between messages. How can I call ReadAsync()? There’s no TryDequeue or Dequeue, if there’s no message in the queue, it runs NULL, right?
The answer is that Channel Reader’s “ReadAsync()” method actually “waits” for a message. Therefore, there is no need to execute some ridiculous loop while waiting for a message, and there is no need to completely block the thread while waiting. We’ll talk more about this in a future article, but be aware that you can use ReadAsync to wait for new messages rather than writing some custom code to do the same thing.
What’s next?
Now that you have the basics down, let’s look at some more advanced scenarios for using channels in the next article.
Welcome to pay attention to my public number – code non translation station, if you have a favorite foreign language technical articles, you can recommend to me through the public number message.