并发集合 System.Collections.Concurrent

 

转载原文网址:https://www.cnblogs.com/springsnow/p/9511514.html 

 

一、概述

System.Collections.Concurrent 命名空间提供多个线程安全集合类。

当有多个线程并发访问集合时,应使用这些类代替 System.Collections 和 System.Collections.Generic 命名空间中的对应类型。

为了对集合进行线程安全的访问,定义了 IProducerConsumerCollection接口。这个接口中最重 要的方法是TryAdd()和TryTake()。

  1. TryAdd()方法尝试给集合添加一项,但如果集合禁止添加项,这个操作就可能失败。为了给出相关信息,TryAdd()方法返回一个布尔值,以说明操作是成功还是失败。
  2. TryTake()方法也以这种方式工作,以通知调用者操作是成功还是失败,并在操作成功时返回集合中的项

二、空间中包含的类

ConcurrentXXX :这些集合是线程安全的,如果某个动作不适用于线程的当前状态,它们就返回false。在继续之前,总是霈要确认添加或提取元素是否成功。不能相信集合会完成任务。

1、ConcurrentQueue 队列

这个集合类用一种免锁定的算法实现,使用在内部合并到一个链表中的32项数组。

访问队列元素的方法有Enqueue()、TryDequeue()和TryPeek()。这些方法的命名非常类似于前面Queue类的方法,只是给可能调用失败的方法加上了前缀Try。

因为这个类实现了IProducerConsumerCollection接口,所以 TryAdd()和 TryTake()方法仅调用 Enqueue()和 TryDequeue()方法。

 ConcurrentQueue<byte> queue = new ConcurrentQueue<byte>();
            queue.Enqueue(0x01);
            queue.TryDequeue( out var data);//0x01
            queue.TryDequeue(out var data2);//0x00
            queue.Enqueue(0x02);
            queue.TryPeek(out var data3);//0x02
            queue.TryPeek(out var data4);//0x02,without removing it

 

2、ConcurrentStack 堆栈

非常类似于ConcurrentQueue类,只是带有另外的元素访问方法。

ConcurrcntStack类定义了 Push()、PushRange()、TryPeek()、TiyPop()和 TryPopRange() 方法。在内部这个类使用其元素的链表。

  ConcurrentStack<byte> stack = new ConcurrentStack<byte>();
            stack.Push(0x01);
            stack.PushRange(new byte[] { 0x02, 0x03, 0x04});
            stack.TryPeek(out var data1);//0x04
            stack.TryPeek(out var data2);//0x04
            stack.TryPeek(out var data3);//0x04
            stack.TryPop(out var data4);//0x04
            stack.TryPop(out var data5);//0x03
            byte[] arr = new byte[5];
            stack.TryPopRange(arr);//0x02,0x01,0x00,0x00,0x00

 

3、ConcurrentBag 包

该类没有定义添加或提取项的任何顺序。这个类使用一个把线程映射到内部使用的数组上的概念,因此尝试减少锁定。

访问元素的方法有Add()、TryPeek()和 TryTake()。

 ConcurrentBag<byte> bag = new ConcurrentBag<byte>();
            bag.Add(0x01);
            bag.Add(0x02);
            bag.Add(0x03);
            bag.TryPeek(out var data1);//0x03
            bag.TryPeek(out var data2);//0x03
            bag.TryTake(out var data3);//0x03
            bag.TryTake(out var data4);//0x02

 

4、ConcurrentDictionary<tkey, tvalue=""> 字典

——这是一个线程安全的键值集合。

TryAdd()、TryGetValue()、TryRemove()和TryUpdate()方法以非阻塞的方式访问成员。

因为元素基于键和值, 所以 ConcurrentDictionary<tkey,tvalue>没有实现 IProducerConsumerCollection。

  ConcurrentDictionary<string, int> dic = new ConcurrentDictionary<string, int>();
            dic.TryAdd("one", 1);
            dic.TryAdd("two", 2);
            dic.TryAdd("three", 3);
            dic.TryAdd("three", 4);
          var data1=  dic.GetOrAdd("one", 3);//0x01
          var data2=  dic.AddOrUpdate("one", 3, (key, value) =>
            {
                return value + 1;
            });//0x02

 

5、BlockingCollection 集合

这个集合在可以添加或提取元素之前,会阻塞线程并一直等待。

BlockingCollection集合提供了一个接口,以使用Add()和Take()方法来添加和删除元素。 这些方法会阻寒线程,一直等到任务可以执行为止。

Add()方法有一个重载版本,其中可以给该重载版本传递一个CancellationToken令牌。

这个令牌允许取消被阻塞的调用。如果不希望线程无限期地等待下去,且不希望从外部取消调用,就可以使用TryAdd()和 TryTake()方法,在这些方法中,也可以指定一个超时值,它表示在调用失败之前应阻塞线程和等待的最长时间。

 

这是对实现了 IProducerConsumerCollection接口的任意类的修饰器,它默认使用ConcurrentQueue类。

还可以给构造函数传递任何其他实现了 IProducerConsumerCollection 接口的类。

下面的代码示例简单演示了使用BlockingCollection类和多个线程的过程。

一个线程是生成器,它使用Add()方法给集合写入元素,另一个线程是使用者,它使用Take()方法从集合中提取元素:

  static void Main(string[] args)
        {
            BlockingCollection<int> collection = new BlockingCollection<int>();

            var producer = Task.Run(() =>
              {
                  Enumerable.Range(1, 100).ToList().ForEach(r =>
                  {
                      collection.Add(r);
                      Console.WriteLine($"set {r};{Environment.TickCount}");
                  });


              }).GetAwaiter();

            var consumer = Task.Run(() =>
            {
                while (true)
                {
                    var IsDequeue = collection.TryTake(out int item, 1000);
                    if (IsDequeue)
                    {
                        Console.WriteLine($"get {item};{Environment.TickCount}");
                    }
                    else
                    {
                        Console.WriteLine($"get null;{Environment.TickCount}");
                    }
                }
            }).GetAwaiter();


            consumer.GetResult();
            producer.GetResult();
            Console.Read();

        }

 

posted @ 2021-11-25 16:17  流沙河小妖  阅读(457)  评论(0编辑  收藏  举报