C# Channel
命名空间:System.Threading.Channels
Channel这个概念和C#的async stream有很多关联。可以把Channel看作是一个消息管道或者生产者/订阅者模型。生产者将消息放入Channel的一端,而订阅者/消费者从另一端消费消息。
Channel可以保证消息的顺序性。
Channel分为限容Channel(BoundedChannel)和不限容Channel(UnBundedChannel)。Channel主要通过自身的静态方法进行实例化:
var channel = Channel.CreateUnbounded<int>(); //不限容Channel var channel=Channel.CreateBounded<int>(); //限容Channel
在Channel中,生产者也叫做Writer,消费者就是Reader,我们来看一下这个例子:
using System.Threading.Channels; var channel = Channel.CreateUnbounded<int>(); Task.Run(async () => { for (int i = 0; i < 10; i++) { await Task.Delay(TimeSpan.FromMilliseconds(200)); await channel.Writer.WriteAsync(i);// 生产者写入消息 if (i > 5) { channel.Writer.Complete(); //生产者也可以明确告知消费者不会发送任何消息了 } } }); Task.Run(async () => { await foreach (var item in channel.Reader.ReadAllAsync())//async stream,在没有被生产者明确Complete的情况下,这里会一致阻塞下去 { Console.WriteLine(item); } Console.WriteLine("done"); }); Console.ReadKey();
上述例子创建了两个线程来模拟生产者和消费者。
消费者的“订阅”主要是由以下几种方式来进行的:
1、Channel.Reader.WaitToReadAsync()。这个方法会在当有数据可用时返回true
2、Channel.Reader.ReadAllAsync()。注意这个方法返回的是一个IAsyncEnumerable。IAsyncEnumerable是C#8推出的一种异步流模型。我们可以使用await foreach这种方式来消费异步流。在Channel中,如果Writer没有明确Complete,那么await foreach也会成为一个死循环。所以在使用这个模型前,需要注意。
3、Channel.Reader.ReadAsync()。
生产者的“生产”主要是由以下几种方式来进行的:
1、Channel.Writer.WriteAsync(),生产一条消息。
2、Channel.Writer.WaitToWriteAsync()。当有空间可写时,返回一个true。因为Channel有限容类型的Channel,所以这个方法也可以作为一个屏障,当Channel空间已满时,进行阻塞。
3、Channel.Writer.Complete()。发送一个信号,告知Reader消费者,Channel已经不会再发送任何消息了。此时Channel的状态变为closed,如果Reader继续读取信息,则会抛异常:
System.Threading.Channels.ChannelClosedException:“The channel has been closed.”