accepted answer to question "Why does this Parallel.ForEach code freeze the program up?"建议在WPF应用程序中用ConcurrentBag替换List使用情况。
我想了解在这种情况下是否可以使用BlockingCollection代替?
答案 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)
是的,您可以使用BlockingCollection
。 finishedProxies
将被定义为:
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)
,在大多数情况下,List
和ConcurrentBag
都不是最佳选择。这两个集合都专用于促进生产者-消费者的情况,因此,除非您有多个线程同时从集合中添加和来删除项目,否则应寻找其他选项(最好的选择是ConcurrentQueue
(在大多数情况下)。
特别是对于ConcurrentBag
来说,它是一个非常专业的类,针对混合生产者-消费者方案,这意味着每个工作线程既是生产者又是消费者。它可以很好地用于ObjectPool
类的内部存储,但是除此之外,很难想象该类有任何有利的使用场景。例如,将其用于存储BlockingCollection
循环的结果是一个错误的选择,因为该类并未针对此用法进行优化。 Parallel.ForEach
不仅效率更高,而且在保留插入项目的顺序方面也做得更好。
人们通常认为ConcurrentQueue
与ConcurrentBag
是线程安全的等效项,但事实并非如此。两种API的相似性具有误导性。将List
调用到Add
会导致在列表末尾添加项目。将Add
调用到List
会导致在袋子内的随机插槽处添加物品。 ConcurrentBag
本质上是无序的。它并未针对枚举进行优化,并且在被命令这样做时做得很糟糕。它在内部维护着多个线程本地队列,因此其内容的顺序由哪个线程执行了什么而不是什么时候发生的事情决定。
ConcurrentQueue.Enqueue
方法是ConcurrentBag
的更好的线程安全替代方法。与“添加” 相比,“入队” 是一个不太熟悉的词(而且更难发音!),但实际上它可以实现您期望的作用。
List.Add
做不到ConcurrentBag
做不到的事情。例如,两个集合都不提供从集合中remove a specific item的方式。如果要使用带有ConcurrentQueue
参数的TryRemove
方法的并发集合,则应查看ConcurrentDictionary
类。