关于blockingCollection<T>, bufferblock<T>, Channel<T>引发的思考

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

}

参考:.NET 进程内队列 Channel 的入门与应用 - 元视角

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破浪征程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值