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

这篇文章是.NET中有关Channel的系列文章的一部分。当然,最好从第1部分开始,但是您可以使用下面的链接跳过任何想要的地方。这系列文章均是本人翻译,翻译也是随性而至,并非直译,英文好的可以去看原文,译文可以随便转载,但请注明出处!

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

这里很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);
    }
}

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

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

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

揭晓答案!

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

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

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

5、接下来是什么?

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

6、小结

选择这个专题,也是为了共同熟悉,共同进步!关注楼主,不迷路,我是网尘。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值