【问题标题】:When to use BlockingCollection and when ConcurrentBag instead of List<T>?何时使用 BlockingCollection,何时使用 ConcurrentBag 而不是 List<T>?
【发布时间】:2013-03-02 06:18:06
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

    创建BlockingCollection&lt;T&gt;对象时,可以指定不 只有有限的容量,还有要使用的集合类型。为了 例如,您可以为先入指定一个ConcurrentQueue&lt;T&gt; 对象, 先出 (FIFO) 行为,或最后一个 ConcurrentStack&lt;T&gt; 对象 进先出 (LIFO) 行为。您可以使用任何集合类 实现IProducerConsumerCollection&lt;T&gt; 接口。默认 BlockingCollection&lt;T&gt; 的集合类型是 ConcurrentQueue&lt;T&gt;

    这包括ConcurrentBag&lt;T&gt;,这意味着您可以有一个阻塞并发包。那么普通的IProducerConsumerCollection&lt;T&gt; 和阻塞集合有什么区别呢? BlockingCollection 的文档说(强调我的):

    BlockingCollection&lt;T&gt; 用作包装器 IProducerConsumerCollection&lt;T&gt; 实例,允许删除尝试 从集合中阻止,直到有数据可供删除。 同样,可以创建BlockingCollection&lt;T&gt;强制执行 允许的数据元素数量的上限 IProducerConsumerCollection&lt;T&gt; [...]

    由于在链接的问题中没有必要做这些事情,使用BlockingCollection 只是添加了一层未使用的功能。

    【讨论】:

    • @Jon,谢谢,这对我帮助很大,让我摆脱了白痴状态,并在我真正需要 ConcurrentDictionary 时不再浪费时间研究 ConcurrentBag 和 BlockingCollection
    【解决方案2】:
    • List&lt;T&gt; 是一个设计用于单线程的集合 应用程序。

    • ConcurrentBag&lt;T&gt;Collections.Concurrent命名空间设计的一个类 简化在多线程环境中使用集合。如果你 使用 ConcurrentCollection 你将不必锁定你的 收集以防止其他线程损坏。你可以插入 或从您的集合中获取数据,而无需编写特殊的锁定代码。

    • BlockingCollection&lt;T&gt; 旨在摆脱检查线程之间共享集合中是否有新数据可用的要求。如果有新数据插入到共享集合中,那么您的消费者线程将立即唤醒。因此,您不必在特定时间间隔(通常在 while 循环中)检查消费者线程是否有新数据可用。

    【讨论】:

    • 我从反编译器中看不到 ConcurrentCollection&lt;T&gt; 的类: public class ConcurrentBag : IProducerConsumerCollection, IEnumerable, IEnumerable, ICollection, IReadOnlyCollection
    • 我仍然给了 +1 - 它有助于澄清 BlockingCollection 上的消费者线程唤醒 谢谢!
    【解决方案3】:

    当您发现需要线程安全的List&lt;T&gt; 时,大多数情况下ConcurrentBag&lt;T&gt;BlockingCollection&lt;T&gt; 都不是您的最佳选择。这两个集合都专门用于促进生产者-消费者场景,因此除非您有多个线程同时添加从集合中删除项目,否则您应该寻找其他选项(最佳候选者是ConcurrentQueue&lt;T&gt; 在大多数情况下)。

    特别是关于ConcurrentBag&lt;T&gt;,它是一个针对混合生产者-消费者场景的非常专业的类。这意味着每个工作线程都应该是生产者和消费者(添加从同一集合中删除项目)。它可能ObjectPool 类的内部存储的良好候选者,但除此之外,很难想象该类有任何有利的使用场景。

    人们通常认为ConcurrentBag&lt;T&gt;List&lt;T&gt; 的线程安全等价物,但事实并非如此。这两个 API 的相似性具有误导性。将Add 调用到List&lt;T&gt; 会导致在列表末尾添加一个项目。将Add 调用到ConcurrentBag&lt;T&gt; 会导致将项目添加到包内的随机插槽中。 ConcurrentBag&lt;T&gt; 本质上是无序的。它被枚举为not optimized,当它被命令这样做时,它的工作很糟糕。它在内部维护了一堆线程本地队列,因此其内容的顺序取决于哪个线程做了什么,而不是什么时候发生的。

    这些特性使得ConcurrentBag&lt;T&gt; 不是存储Parallel.For/Parallel.ForEach 循环结果的理想选择。

    List&lt;T&gt;.Add 的更好的线程安全替代方法是 ConcurrentQueue&lt;T&gt;.Enqueue 方法。 “Enqueue” 是一个不如 “Add” 熟悉的词,但它实际上做了你期望它做的事情。

    没有什么是ConcurrentBag&lt;T&gt; 可以做到的,而ConcurrentQueue&lt;T&gt; 则不能。例如,这两个集合都没有提供从集合中到 remove a specific item 的方法。如果你想要一个带有 key 参数的 TryRemove 方法的并发集合,你可以查看 ConcurrentDictionary&lt;K,V&gt; 类。

    【讨论】:

      【解决方案4】:

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

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

      要添加一个项目,你可以这样写:

      finishedProxies.Add(checkResult);
      

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

      【讨论】:

      • Jim Mischel,阅读您的.NET Reference Guide。我希望我能早点找到它,如此接近我的实际需求
      猜你喜欢
      • 2023-04-11
      • 2015-09-29
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-30
      • 2011-04-15
      相关资源
      最近更新 更多