14

Dotnet Core下的Channel, 你用了吗?

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MjM5MjQwMDUzMw%3D%3D&%3Bmid=2247484138&%3Bidx=1&%3Bsn=d0f37e300e658f96505dd922998a33a0
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天给大家分享一个微软官方的好东西:Channel。

前言

今天给大家分享一个微软官方的生产者/消费者方案的特性解决:Channel。

Channel在 System.Threading.Channels 命名空间下,Core 2.1使用时,需要从Nuget上安装。

% dotnet add package System.Threading.Channels

而在Core 3.0 preview 7开始,就直接包含在框架中了。

这是一个相对较新的特性。从Core 2.1开始加入,现在版本是5.0.0(嗯,这个版本号有点骗人,Channel的第一个版本就是4.5.0)。

Channel能做什么?

逻辑上,Channel实际就是一个高效的、线程安全的队列,支持在生产者和消费者之间传递数据。

利用Channel,通过发布和订阅,可以将生产者和消费者分开。生产者Producer负责接收请求,并写入Channel,而消费者Consumer为每个进入Channel的数据执行处理。这样做,一方面可以使生产者和消费者并行工作来提高性能,另一方面,可以通过创建更多的生产者或消费者来提高应用的吞吐量。

下面,我们以一个实际例子,来解释这个特性。

创建Channel

Channel提供了一个静态Channel类,提供了两个公开方法来创建两种类型的Channel。

  • CreateUnbounded - 创建一个具有无限容量的Channel。

  • CreateBounded - 创建一个具有有限容量的Channel。

人通常来说,这两种方式使用上没有太大的区别。实际应用中,具体要看生产和消费的速度,以及期望产生的结果。有限容量的Channel,容量是有上限的,到达上限后,可以让生产者非阻塞等待消费者使用并释放Channel容量后再继续。这种方式,好处是可以控制生产的速度,控制系统资源的使用,缺点也是。因为控制速度意味着生产速度会被限制,甚至停止。而无限容量,生产者可以全速进行生产。但也有缺点,如果消费者的消费速度低于生产者,Channel的资源使用会无限增加,会有服务器资源耗尽的可能。

今天的例子,我们使用无限Channel。

var channel = Channel.CreateUnbounded<string>();

非常简单的一行代码,就创建了一个无限容量的Channel。

我们定义这个Channel用来保存字符串对象。

创建方法是一个通用的工厂方法,所以我们可以为需要使用的任何类型的对象和数据创建Channel。

Channel有两个属性:阅读器返回ChannelReader,写入器返回ChannelWriter。

写入Channel

使用写入器ChannelWriter,可以对Channel进行写入操作。ChannelWriter提供了以下几个方法:

  • WriteAsync - 异步写入

  • WaitToWriteAsync - 非阻塞等待,直到有空间可写入时或Channel关闭时,返回true/false

  • TryWrite - 尝试写入

  • Complete - 标记Channel为关闭,并不再写入数据到该Channel

  • TryComplete - 尝试标记Channel为关闭。

这几个方法很容易理解,就不解释了。

在本文的例子里,我用了:

await channel.Writer.WriteAsync("New message");

读取Channel

使用阅读器ChannelReader从Channel进行数据的读取。也提供了几个方法:

  • ReadAsync - 异步读取

  • ReadAllAsync - 异步读取Channel中的所有数据

  • TryRead - 尝试读取

  • WaitToReadAsync - 非阻塞等待,直到有数据可读取或Channel关闭时,返回true/false

不同的消费者模式,会用到不同的读取方法。这个根据经验来写就好。

本文的例子中,我是采用WaitToReadAsync和ReadAsync配合来使用的:

while (await ChannelReader.WaitToReadAsync())
{
    if (ChannelReader.TryRead(out var timeString))
    {
          /***/
    }
}

WaitToReadAsync是一个非阻塞等待,在有消息可读或Channel关闭时,才会唤醒并继续。

考虑到有多个消费者的情况,有可能别的线程已经进行了读取,这儿使用TryRead进行读取操作。

要注意:数据的同步工作是由Channel进行管理的。Channel会确保多个消费者不会读到相同的数据。Channel同时也管理数据的次序。

示例代码

今天的示例代码我放到了Github上。链接是文章最后。

这个例子中,我做了三个场景。

首先是Channel。我使用了无限Channel。然后是创建生产者和消费者。数据传输过程就简单化了,生产者只简单将一个字符串写入到Channel。消费者也是,简单等待并从Channel读取数据字符串,写入控制台。

三个场景分别是:

单一生产者/单一消费者

这个例子中,创建了一个生产者和一个消费者。两者的任务都是并发启动的。

里面的延时,是用来模拟工作负载的。

多个生产者/单一消费者

这个例子中有两个生产者。通常在应用中有多个生产者时,我们需要确保生产与单个消费者所能处理的消息数量大致相当,这样能更好地利用服务器资源。

单一生产者/多个消费者

这个其实是应用中最常见的情况,就是产生消息很快,但处理工作相关较慢,而且工作也更密集。这种情况,实际应用中我们可以通过扩大消费者数量来满足生产的需求。

总结

最近的项目在做一个大数据的采集,用到了一些Channel的技术。然后发现网上这部分内容很少,就做了个例子,写了这个文章。

Channel内容本身并不多,但用着很方便,而且实际应用中,比想像的更强大。它可以简化很多生产者/消费者模式的使用,而且,任务间交换数据,使用Channel会更方便,更直接。

示例代码在:https://github.com/humornif/Demo-Code/tree/master/0033/demo

喜欢就来个三连,让更多人因你而受益


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK