何时使用BlockingCollection和ConcurrentBag而不是List <t>?</t>

时间:2013-03-14 02:30:58

标签: c# wpf multithreading linq task-parallel-library

accepted answer to question "Why does this Parallel.ForEach code freeze the program up?"建议在WPF应用程序中用ConcurrentBag替换List使用情况。

我想了解在这种情况下是否可以使用BlockingCollection代替?

4 个答案:

答案 0 :(得分:65)

你确实可以使用BlockingCollection,但这样做绝对没有意义。

首先,请注意BlockingCollection是实现IProducerConsumerCollection<T>的集合的包装器。实现该接口的任何类型都可以用作底层存储:

  

创建BlockingCollection<T>对象时,可以不指定   只有有界容量,但也要使用的集合类型。对于   例如,您可以先指定ConcurrentQueue<T>个对象,   先出(FIFO)行为,或最后一个ConcurrentStack<T>对象   in,first out(LIFO)行为。您可以使用任何集合类   实现IProducerConsumerCollection<T>接口。默认   BlockingCollection<T>的集合类型为ConcurrentQueue<T>

这包括ConcurrentBag<T>,这意味着您可以拥有阻止并发包。那么普通IProducerConsumerCollection<T>和阻塞集合之间的区别是什么? BlockingCollection的文档说(强调我的):

  

BlockingCollection<T>用作for的包装器   IProducerConsumerCollection<T>个实例,允许删除尝试   从收集到阻止,直到可以删除数据。   同样,可以创建BlockingCollection<T>强制执行   上限允许的数据元素数量   IProducerConsumerCollection<T> [...]

由于在链接问题中没有必要执行上述任何一项操作,因此使用BlockingCollection只会添加一层未使用的功能。

答案 1 :(得分:11)

  • List<T>是一个旨在用于单线程的集合 应用

  • ConcurrentBag<T>ConcurrentCollection<T>设计的子类型 简化在多线程环境中使用集合。如果你 使用ConcurrentCollection你不必锁定你的 集合以防止其他线程的损坏。你可以插入 或者从您的收藏中获取数据而无需编写特殊的锁定代码。

  • BlockingCollection<T>旨在摆脱对此的要求 检查之间的共享集合中是否有新数据可用 线程。如果有新数据插入共享集合而不是您的 消费者线程将无法清醒。所以你不必检查 如果新数据在特定时间内可供消费者线程使用 间隔通常在while循环中。

答案 2 :(得分:3)

是的,您可以使用BlockingCollectionfinishedProxies将被定义为:

BlockingCollection<string> finishedProxies = new BlockingCollection<string>();

并添加一个项目,你会写:

finishedProxies.Add(checkResult);

完成后,您可以根据内容创建一个列表。

答案 3 :(得分:-1)

无论何时发现需要线程安全的 serializer.Serialize(new { Books = books.Select((x, i) => new Dictionary<string, string> { { $"{nameof(x.Name}-{i+1}", x.Name }, { $"{nameof(x.Type}-{i+1}", x.Type } }}); However, it looks very WIERD to have such a Json (I mean with indexes as property names of objects in same array) ,在大多数情况下,ListConcurrentBag都不是最佳选择。这两个集合都专用于促进生产者-消费者的情况,因此,除非您有多个线程同时从集合中添加来删除项目,否则应寻找其他选项(最好的选择是ConcurrentQueue(在大多数情况下)。

特别是对于ConcurrentBag来说,它是一个非常专业的类,针对混合生产者-消费者方案,这意味着每个工作线程既是生产者又是消费者。它可以很好地用于ObjectPool类的内部存储,但是除此之外,很难想象该类有任何有利的使用场景。例如,将其用于存储BlockingCollection循环的结果是一个错误的选择,因为该类并未针对此用法进行优化。 Parallel.ForEach不仅效率更高,而且在保留插入项目的顺序方面也做得更好。

人们通常认为ConcurrentQueueConcurrentBag是线程安全的等效项,但事实并非如此。两种API的相似性具有误导性。将List调用到Add会导致在列表末尾添加项目。将Add调用到List会导致在袋子内的随机插槽处添加物品。 ConcurrentBag本质上是无序的。它并未针对枚举进行优化,并且在被命令这样做时做得很糟糕。它在内部维护着多个线程本地队列,因此其内容的顺序由哪个线程执行了什么而不是什么时候发生的事情决定。

ConcurrentQueue.Enqueue方法是ConcurrentBag的更好的线程安全替代方法。与“添加” 相比,“入队” 是一个不太熟悉的词(而且更难发音!),但实际上它可以实现您期望的作用。

List.Add做不到ConcurrentBag做不到的事情。例如,两个集合都不提供从集合中remove a specific item的方式。如果要使用带有ConcurrentQueue参数的TryRemove方法的并发集合,则应查看ConcurrentDictionary类。