在我们之前的文章中,看了一些非常简单的例子来说明Channel是如何工作的,我们看到了一些非常漂亮的特性,但大多数情况下它与其他某某Queue实现非常相似。让我们进入一些更高级的话题。我说的是高级,但其中很多都非常简单。

读/写分离

如果你曾经在两个类之间共享队列,你就会知道任何一个类都可以读/写,即使它们本不应该这样做。例如:

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

因此,生产者应该只写队列,消费者应该只读队列,在这两种情况下,它们可以对队列执行所有操作。虽然你可能在自己的脑海中希望消费者只读取,但另一个开发人员可能会出现调用Enqueue,除了代码审查之外,没有什么可以阻止他们犯这个错误。

但是有了Channel,我们可以做不同的事情。

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

在这个例子中,我添加了一个main方法来向你展示如何创建writer/reader,但它非常简单。这里我们可以看到,对于我们的生产者,我只传递给它一个ChannelWriter,所以它只能做写操作。对于我们的消费者,我们传递给它一个ChannelReader,所以它只能读取。

当然,这并不意味着其他开发人员不能修改代码并开始注入根Channel对象,或者同时传入ChannelWriter/ChannelReader,但这至少比之前的情况要好得多。

完成一个Channel

我们在前面看到,当在通道上调用ReadAsync()时,它实际上会在那里等待消息,但是如果没有更多的消息到来呢?对于.net中的其他队列,我们通常需要传递某种共享的布尔值或一个CancellationToken。但有了Channel,就更容易了。

考虑以下几点:

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

我让第二个线程尽可能快地写入我们的Channel,然后完成它。然后我们的读取器缓慢读取,每次读取之间有1秒的延迟。注意,我们捕获了ChannelClosedExecption,当你尝试从关闭通道读取最后消息之后时将调用它。

我只是想说清楚。在Channel上调用Complete()不会立即关闭通道并杀死读取该通道的所有人。而是通知所有服务,一旦最后一条消息被读取,我们就完成了。这很重要,因为这意味着当我们等待新条目时,当队列是空的时,当队列是满的时,是否调用Complete()都无关紧要。我们可以肯定,我们将完成所有可得到的工作。

在Channel中使用IAsyncEnumerable

以我们试图关闭一个Channel为例,有两件事引起了我的注意。

我们有一个while(true)循环。这并不是真的那么糟糕,但它有点碍眼。

为了打破这个循环,并知道Channel已经完成,我们必须捕获异常并将其吞下。

使用命令“ReadAllAsync()”来解决这些问题,它返回一个IAsyncEnumerable。代码看起来有点像这样:

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

现在代码读起来好多了,并且去掉了捕获异常的一些多余的东西。因为我们使用的是IAsyncEnumerable,所以我们仍然可以像以前那样等待每一项,但是我们不再需要捕获异常,因为当Channel完成时,它只是简单地说没有其他东西了,然后循环退出。

同样,这消除了在处理队列时必须编写的一些混乱代码。以前你必须编写某种无限循环,而现在它只是一个真正整洁的循环,可以处理底层的所有东西。

接下来是什么

到目前为止,我们一直在使用“无限的”通道。你可能已经猜到了,当然也可以选择使用BoundedChannel。查看本系列的下一部分,更好地理解这些东西。

欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。

原文链接:https://dotnetcoretutorials.com/2020/11/24/using-channels-in-net-core-part-2-advanced-channels/

在.NET Core中使用Channel(二)的更多相关文章

  1. 在.NET Core中使用Channel(一)

    我最近一直在熟悉.net Core中引入的新Channel<T>类型.我想在它第一次发布的时候我了解过它,但是有关文章非常非常少,我不能理解它们与其他队列有什么不同. 在使用了一段时间后, ...

  2. (12)ASP.NET Core 中的配置二(Configuration)

    1.内存配置 MemoryConfigurationProvider使用内存中集合作为配置键值对.若要激活内存中集合配置,请在ConfigurationBuilder的实例上调用AddInMemory ...

  3. IdentityServer4在Asp.Net Core中的应用(二)

    继续上次授权的内容,客户端模式后我们再说以下密码模式,先回顾下密码模式的流程: 我们还是使用上次的代码,在那基础上修改,在IdentityServer4里面有一个IdentityServer4.Tes ...

  4. 在.NET Core中使用Channel(三)

    到目前为止,我们一直在使用所谓的"Unbounded"通道.你会注意到,当我们创建通道时,我们这样做: var myChannel = Channel.CreateUnbounde ...

  5. (13)ASP.NET Core 中的选项模式(Options)

    1.前言 选项(Options)模式是对配置(Configuration)的功能的延伸.在12章(ASP.NET Core中的配置二)Configuration中有介绍过该功能(绑定到实体类.绑定至对 ...

  6. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  7. gRPC在 ASP.NET Core 中应用学习(二)

    前言: 上一篇文章中简单的对gRPC进行了简单了解,并实现了gRPC在ASP.NET Core中服务实现.客户端调用:那么本篇继续对gRPC的4中服务方法定义.其他使用注意点进一步了解学习 一.gRP ...

  8. ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)

    前言 本文主要是详解一下在ASP.NET Core中,自带的IOC容器相关的使用方式和注入类型的生命周期. 这里就不详细的赘述IOC是什么 以及DI是什么了.. emm..不懂的可以自行百度. 目录 ...

  9. 深入理解net core中的依赖注入、Singleton、Scoped、Transient(二)

    相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...

随机推荐

  1. 用Python爬取了三大相亲软件评论区,结果...

    小三:怎么了小二?一副愁眉苦脸的样子. 小二:唉!这不是快过年了吗,家里又催相亲了 ... 小三:现在不是流行网恋吗,你可以试试相亲软件呀. 小二:这玩意靠谱吗? 小三:我也没用过,你自己看看软件评论 ...

  2. Java 中的语法糖,真甜。

    我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star https://github.com/crisxuan/bestJavaer 我们在日常开发中经常会使用到诸如泛型.自动拆箱和装箱 ...

  3. 【面试】关于get和post两种方法的不同。

    最近在面试题和笔试题中经常会看到这道题,所以打算系统的整理一下. 一般标准的答案是这样的. GET在浏览器回退时是无害的,而POST会再次提交请求(浏览器应该告知用户数据会被重新提交). GET产生的 ...

  4. python菜鸟教程学习10:数据结构

    列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...

  5. Java 线程安全问题的本质

    原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: 线程安全问题的本质 理解CPU JVM虚拟机类比于操作系统 重排序 汇总 一些解释 ...

  6. 图论补档——KM算法+稳定婚姻问题

    突然发现考前复习图论的时候直接把 KM 和 稳定婚姻 给跳了--emmm 结果现在刷训练指南就疯狂补档.QAQ. KM算法--二分图最大带权匹配 提出问题 (不严谨定义,理解即可) 二分图 定义:将点 ...

  7. 阿里不允许使用 Executors 创建线程池!那怎么使用,怎么监控?

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 五常大米好吃! 哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃, ...

  8. 基于excel实现接口自动化测试

    本文档介绍如何使用excel管理接口测试用例并一键执行的实现方式,其中包括 python 读写excel, request库的基本操作,接口用例的设计 接口用例设计 用例字段描述 被依赖表达式: 示例 ...

  9. Google、Facebook等均开始支持的HTTP3到底是个什么鬼?

    GitHub 19k Star 的Java工程师成神之路,不来了解一下吗! 最近一段时间以来,关于HTTP/3的新闻有很多,越来越多的国际大公司已经开始使用HTTP/3了. 所以,HTTP/3已经是箭 ...

  10. 初接触matplotlib

    1,绘制简单的折线图. 1 import matplotlib.pyplot as plt 2 3 square = [1,4,9,16,25] 4 5 plt.plot(square) 6 plt. ...