This article is part of a series of articles about channels in.NET. Of course, it’s best to start with Part 1, but you can skip anywhere you want using the links below. This series of articles are my translation, translation is also spontaneous, not literal translation, good English can go to see the original, translation can be freely reproduced, but please indicate the source!
- Part 1 — Getting started with channels
- Part 2 — Advanced channels
- Part 3 — Understanding back pressure
1, confused
It’s painful when a new concept comes out and you want to use it, but you can’t understand it directly.
For the passage, I think I have a problem! I’ve been getting familiar with it. The Channel type introduced in NET Core 3.X. But there were very, very few articles, and I couldn’t understand how they were different from other queues.
After using them for a while, I finally saw their great appeal and real power.
The most important channels to watch are the large asynchronous background operations, which almost all require two-way communication to synchronize what they are doing. If you’ve gone through this series, you’ll know I’m right, and you should be able to learn when to use Channel
, and when to use something more basic like Queue
.
2. What is Channel
First, a Channel is essentially a new collection type in.NET that is very similar to the existing Queue
type, with some differences.
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.
The following illustration shows the channel concept in RabbitMQ, which is a communication abstraction based on TCP links to save and share them.
But if you think of a Channel in.NET as just a queue, with some additional logic that allows it to wait for new messages, to tell producers that the queue is busy, and to provide strong thread-safety support, it doesn’t seem wrong.
I mentioned a key word here, producer/consumer. You may also have heard of Pub/Sub, which is not the same thing! .
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 are copies of everyone getting the same message, and here’s how they differ.
In chart form, Pub/Sub looks something like this:Producer/consumer describes the behavior of a producer publishing a message that can be acted upon by one or more consumers,But each message is read only once. It will not be distributed to every subscriber.
Of course, in chart form:Haha, should make it clear, the nonsense of the original author XXX was deleted a number of… .
This is often referred to as the producer-consumer problem, and it’s the problem Channel is trying to solve.
3. Channel example
Classes are related to the Channel in the System. The Threading. Channels.
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 = awaitmyChannel.Reader.ReadAsync(); Console.WriteLine(item); }}Copy the code
It’s Easy here.
We created an “infinite” channel (which means it can hold an infinite number of items). 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.
4. Channels are thread-safe
Yes, channels are thread-safe. This is very important in a multi-tasking daemon.
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, in the code above, maintaining thread-safety actually has some overhead when we don’t actually need it.
So in that case, we might just use 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 = awaitmyChannel.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.
We call ReadAsync() directly, without any judgment operations like TryDequeue or Dequeue, so if there are no messages in the queue, it will return null, right?
Reveal the answer!
Channel Reader’s “ReadAsync()” method actually “waits” for a message (but does not block).
So, you don’t have to do some ridiculous loop waiting for a message, and you don’t have to completely block the thread waiting for a message.
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.
5. 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.
6, summary
Choose this topic, but also for common familiarity, common progress! Pay attention to the landlord, do not get lost, I am the net dust.