龙空技术网

理解NET Core中的Channel篇之一——通道入门

日行四善 332

前言:

当前兄弟们对“net博客科普”大体比较珍视,小伙伴们都需要剖析一些“net博客科普”的相关内容。那么小编也在网摘上汇集了一些关于“net博客科普””的相关知识,希望我们能喜欢,咱们快快来了解一下吧!

1、迷茫

当一个新概念出来的时候,你很想使用它们,但是又没办法直接理解它,这是一件痛苦的事情。

对于通道,我想我遇到了麻烦!我最近一直在熟悉.NET Core 3.X中引入的Channel类型。但是有关文章非常非常少,我不能理解它们与其他队列有什么不同。

在使用了一段时间后,我终于看到了它们巨大的吸引力和真正的力量。

最值得需要关注通道的是大型异步后台操作,这些操作几乎都需要双向通信来同步它们正在做的事情。如果你们看完本系列,你会清楚我所言不虚,你也应该能学到什么时候使用Channel<T>,什么时候使用一些更基本的东西,比如Queue<T>

2、什么是Channel

首先,Channel本质上是.net中的一种新的集合类型,它与现有的Queue<T>类型非常相似,当然也有不同之处。

在真正尝试研究这个主题时,我发现的问题是,许多现有的外部队列技术(IBM MQ、Rabbit MQ等)都有“channel”的概念,它们的范围从完全抽象的思维过程,到系统中实际的物理类型。

下面图示是rabbitmq中的通道概念,其基于tcp链接之上,为了节约和共享tcp链接,而抽象出的一个通信概念。

但是如果您将.NET中的Channel视为只是一个队列,并且包含一些其他逻辑以使其能等待新消息,并能告知生产者,该队列很繁忙,并且提供了强大的线程安全支持,这样子的理解看起来也没啥错的。

这里我提到了一个关键词,生产者/消费者。你可能还听说过Pub/Sub(发布订阅),这两者之间是不同的!。

Pub/Sub描述的是某人发布信息,一个或多个“订阅者”监听该信息并对其采取一定的响应行为。这里不存在负载平衡,因为当添加订阅服务器时,它们是所有人获得相同消息的副本,下面看看他们的不同之处。

在图表形式中,Pub/Sub看起来有点像这样:

生产者/消费者描述生产者发布消息的行为,并且有一个或多个消费者可以对该消息进行操作,但是每个消息只读取一次。它不会分发到每个订阅者

当然,用图表的形式:

哈哈,应该讲清楚了吧,原作者xxx的废话被删掉若干…。

这通常被称为生产者-消费者问题,这是Channel要解决的问题。

3、Channel示例

与Channel有关的类都在System.Threading.Channels中。

一个极其简单的Channel示例是这样的:

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);    }}123456789101112131415

这里很Easy。

我们创建了一个“无限的”通道(这意味着它可以容纳无限项)。我们写10项,读10项,在这一点上,它与我们在.net中见过的任何其他队列没有太大区别。

4、Channel是线程安全的

没错,通道是线程安全的。 在多任务的后台程序中,这点非常重要。

这意味着多个线程可以读写同一个通道而不会出现问题。如果我们看一下这里的Channel源代码,我们可以看到它是线程安全的,因为它使用锁和内部“队列”的组合来同步读/写器,一个接一个地读/写。

实际上,Channel的预期用例是多线程场景。例如,上面的代码,当我们实际上不需要线程安全性时,维护线程安全实际上会有一些开销。

所以在那个例子中,我们可能只使用Queue<T>就好。但是这段代码呢?

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);    }}12345678910111213141516171819

在这里,我们有一个单独的线程写入消息,而我们的主线程读取消息。

你会注意到有趣的事是,我们添加了延迟。

我们直接调用ReadAsync(),根本没有使用类似TryDequeue或Dequeue这样的判断操作,那如果队列中没有消息,它会返回null,是吗?

揭晓答案!

Channel Reader 的“ReadAsync()”方法实际上会“等待”一个消息(但是不是阻塞)。

所以,你不需要为了等待消息而做一些荒谬的循环,也不需要在为等待消息而完全阻塞线程。

我们将在以后的文章中进一步讨论这个问题,但是你要知道你可以使用ReadAsync来等待新的消息,而不是编写一些自定义的代码来做同样的事情。

5、接下来是什么?

现在你已经掌握了基础知识,下一篇让我们看看使用Channel一些更高级的场景。

原文地址:

标签: #net博客科普