Using channel in. Net core (2)

Time:2021-6-7

In our previous articles, we saw some very simple examples to illustrate how channel works. We saw some very beautiful features, but in most cases it is very similar to other so and so queue implementations. Let’s move on to some more advanced topics. I’m talking about advanced, but many of them are very simple.

Read / write separation

If you’ve ever shared queues between two classes, you’ll know that any class can read / write, even if they shouldn’t have. For example:

class MyProducer
{
    private readonly Queue<int> _queue;

    public MyProducer(Queue<int> queue)
    {
        _queue = queue;
    }
}

class MyConsumer
{
    private readonly Queue<int> _queue;

    public MyConsumer(Queue<int> queue)
    {
        _queue = queue;
    }
}

Therefore, the producer should only write to the queue and the consumer should read only to the queue. In both cases, they can perform all operations on the queue. Although you may wish in your mind that consumers only read, another developer may call enqueue, and nothing can stop them from making this mistake except code review.

But with channel, we can do different things.

class Program
{
    static async Task Main(string[] args)
    {
        var myChannel = Channel.CreateUnbounded<int>();
        var producer = new MyProducer(myChannel.Writer);
        var consumer = new MyConsumer(myChannel.Reader);
    }
}

class MyProducer
{
    private readonly ChannelWriter<int> _channelWriter;

    public MyProducer(ChannelWriter<int> channelWriter)
    {
        _channelWriter = channelWriter;
    }
}

class MyConsumer
{
    private readonly ChannelReader<int> _channelReader;

    public MyConsumer(ChannelReader<int> channelReader)
    {
        _channelReader = channelReader;
    }
}

In this example, I added a main method to show you how to create a writer / reader, but it’s very simple. Here we can see that for our producer, I only pass it a channelwriter, so it can only do write operations. For our consumers, we pass it a channelreader, so it can only read.

Of course, this doesn’t mean that other developers can’t modify the code and start injecting the root channel object, or pass in channelwriter / channelreader at the same time, but it’s at least much better than before.

Complete a channel

As we saw earlier, when readasync() is called on the channel, it will actually wait for messages there, but what if no more messages arrive? For other queues in. Net, we usually need to pass some shared Boolean value or a cancellationtoken. But with channel, it’s easier.

Consider the following:

static async Task Main(string[] args)
{
    var myChannel = Channel.CreateUnbounded<int>();

    _ = Task.Factory.StartNew(async () =>
    {
        for (int i = 0; i < 10; i++)
        {
            await myChannel.Writer.WriteAsync(i);
        }

        myChannel.Writer.Complete();
    });

    try
    {
        while (true)
        {
            var item = await myChannel.Reader.ReadAsync();
            Console.WriteLine(item);
            await Task.Delay(1000);
        }
    }catch(ChannelClosedException e)
    {
        Console.WriteLine("Channel was closed!");
    }
}

I asked the second thread to write to our channel as quickly as possible, and then I finished it. Then our reader reads slowly, with a one second delay between each read. Note that we captured channelclosedexeception, which is called when you try to read the last message from the closed channel.

I just want to be clear. Calling complete() on a channel does not immediately close the channel and kill anyone who reads it. Instead, we inform all services that once the last message is read, we are done. This is important because it means that when we wait for a new entry, when the queue is empty, when the queue is full, it doesn’t matter whether we call complete(). We can be sure that we will finish all the work available.

Using iasyncenumerable in channel

Take our attempt to shut down a channel as an example. Two things caught my attention.

We have a while (true) loop. It’s not really that bad, but it’s a bit of an eyesore.

In order to break the loop and know that the channel is complete, we have to catch the exception and swallow it.

To solve these problems, use the command “readallasync()”, which returns an iasyncenumerable. The code looks a bit like this:

static async Task Main(string[] args)
{
    var myChannel = Channel.CreateUnbounded<int>();

    _ = Task.Factory.StartNew(async () =>
    {
        for (int i = 0; i < 10; i++)
        {
            await myChannel.Writer.WriteAsync(i);
        }

        myChannel.Writer.Complete();
    });

    await foreach(var item in myChannel.Reader.ReadAllAsync())
    {
        Console.WriteLine(item);
        await Task.Delay(1000);
    }
}

Now the code reads much better and removes some of the extraneous things that catch exceptions. Because we are using iasyncenumerable, we can still wait for each item as before, but we no longer need to catch exceptions, because when the channel completes, it simply says there is nothing else, and then loops out.

Again, this eliminates some of the messy code that must be written when processing queues. You used to have to write some kind of infinite loop, but now it’s just a really neat loop that can handle everything at the bottom.

What’s next

So far, we’ve been using “infinite” channels. As you may have guessed, you can also choose to use bounded channel. Check out the next part of this series to better understand these things.

 Welcome to official account official account. If you love foreign technical articles, you can recommend it to me through public comments.
Using channel in. Net core (2)

Recommended Today

What is “hybrid cloud”?

In this paper, we define the concept of “hybrid cloud”, explain four different cloud deployment models of hybrid cloud, and deeply analyze the industrial trend of hybrid cloud through a series of data and charts. 01 introduction Hybrid cloud is a computing environment that integrates multiple platforms and data centers. Generally speaking, hybrid cloud is […]