blockingCollection<T>
为实现 IProducerConsumerCollection<T>的线程安全集合提供阻塞和边界功能。
命名空间 : System.Collections.Concurrent
blockingCollection<T>是一个线程安全的集合类.
使用完该类型需要调用Dispose接口释放,Dispose接口并不是线程安全的,所有其他公共和受保护成员都是线程安全的,可以从多个线程并发使用。
bufferblock<T>
命名空间: System.Threading.Tasks.Dataflow
为数据流提供用于存储数据的缓冲区
Channel<T>
命名空间:System.Threading.Channels
提供支持读取和写入类型 T
的元素的通道的基类。
Channel是生成者/使用者概念编程模型的实现。 在此编程模型中,生成者异步生成数据,使用者异步使用该数据。 换句话说,此模型通过先进先出(“FIFO”)队列将数据从一方传递到另一方。 尝试将通道视为任何其他常见的泛型集合类型,例如 List<T>
。 主要区别在于,此集合管理同步,并通过工厂创建选项提供各种消耗模型。 这些选项可控制通道的行为,例如允许它们存储多少元素、如果达到该限制会发生什么情况,或者通道是否同时由多个生成者或多个使用者访问。
相较前两种类型,此类型发布于.NET Core 3.0,算是比较高级的生成者/消费者模型,blockingCollection实现算是比较早期的实现
性能对比
线程安全的集合类支持的最早版本为.NET Core 1.0
为什么使用Channel
为什么使用channel,我想是一下三点:
1.性能更优异
2.天生就支持多线程(线程安全)
3.实现了真正意义上的读写分离
这一点通过其两个最重要的成员Reader和Writer实现,相较队列而言,并不能保证其读写的单一性
,当我们把队列传递过去,谁也不知道会发生什么
Channel使用实例
internal class Program
{
class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
/// <summary>
/// 创建无界通道
/// </summary>
static Channel<Student> testChannel = Channel.CreateUnbounded<Student>();
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
//******************************并行模式**************************
Action a = async () => {
await Write(testChannel.Writer, 10);
};
Action b = async () =>
{
await Read(testChannel.Reader);
};
Parallel.Invoke([a, a , a, b, b]);
Console.ReadLine();
}
static async Task Write(ChannelWriter<Student> channel, int value)
{
Random random = new Random();
int threadId = Thread.CurrentThread.ManagedThreadId;
for (int i = 0; i < value; i++)
{
Thread.Sleep(1000);
Student student = new Student() { Id = threadId, Name = random.Next(10, 20).ToString() };
if (await channel.WaitToWriteAsync())
{
await channel.WriteAsync(student);
Console.WriteLine($"写入通道内的值:Id :{student.Id} Name:{student.Name}");
}
}
//这里使用complete表示通道已关闭,使得读取器返回false,从而跳出循环正常结束
channel.TryComplete();
Console.WriteLine($"写入完成,线程id:{threadId}");
}
static async Task Read(ChannelReader<Student> channel)
{
int threadId = Thread.CurrentThread.ManagedThreadId;
while (await channel.WaitToReadAsync())
{
channel.TryRead(out Student? value);
//Student value = await channel.Reader.ReadAsync();
if(value != null)
Console.WriteLine($"线程ID:{threadId} 读取通道内的值:Id :{value.Id} Name:{value.Name}");
}
Console.WriteLine($"读取完成:线程Id:{threadId}");
}
}