如何访问阻塞集合的基础默认并发队列

时间:2011-07-06 22:48:08

标签: c# multithreading .net-4.0 task-parallel-library

我有多个生产者和一个消费者。但是,如果队列中存在尚未消耗的内容,则生产者不应再次对其进行排队。 (唯一没有重复的阻塞使用默认并发队列的集合)

if (!myBlockingColl.Contains(item))
    myBlockingColl.Add(item)

然而,blocking coll没有contains方法,也没有提供任何类型的trypeek()方法。如何访问底层并发队列,以便我可以执行类似

的操作
if (!myBlockingColl.myConcurQ.trypeek(item)
  myBlockingColl.Add(item)

尾巴旋转。请帮忙。感谢

3 个答案:

答案 0 :(得分:8)

这是一个有趣的问题。这是我第一次看到有人要求阻止重复的阻塞队列。奇怪的是,我发现BCL中已经存在的东西并不像你想要的那样。我说这很奇怪,因为BlockingCollection可以接受IProducerConsumerCollection作为基础集合,其中TryAdd方法被宣告为在检测到重复项时能够失败。问题是我没有看到IProducerConsumerCollection的具体实现可以防止重复。至少我们可以写自己的。

public class NoDuplicatesConcurrentQueue<T> : IProducerConsumerCollection<T>
{
  // TODO: You will need to fully implement IProducerConsumerCollection.

  private Queue<T> queue = new Queue<T>();

  public bool TryAdd(T item)
  {
    lock (queue)
    {
      if (!queue.Contains(item))
      {
        queue.Enqueue(item);
        return true;
      }
      return false;
    }
  }

  public bool TryTake(out T item)
  {
    lock (queue)
    {
      item = null;
      if (queue.Count > 0)
      {
        item = queue.Dequeue();
      }
      return item != null;
    }
  }
}

既然我们的IProducerConsumerCollection不接受重复,我们可以像这样使用它:

public class Example
{
  private BlockingCollection<object> queue = new BlockingCollection<object>(new NoDuplicatesConcurrentQueue<object>());

  public Example()
  {
    new Thread(Consume).Start();
  }

  public void Produce(object item)
  {
    bool unique = queue.TryAdd(item);
  }

  private void Consume()
  {
    while (true)
    {
      object item = queue.Take();
    }
  }
}

您可能不喜欢我NoDuplicatesConcurrentQueue的实施。如果您认为需要TPL集合提供的低锁性能,您当然可以使用ConcurrentQueue或其他任何方式自行实现。

<强>更新

今天早上我能够测试代码。有一些好消息和坏消息。好消息是,这将在技术上有效。坏消息是您可能不希望这样做,因为BlockingCollection.TryAdd拦截了基础IProducerConsumerCollection.TryAdd方法的返回值,并在检测到false时抛出异常。是的,这是对的。它不像您期望的那样返回false,而是生成异常。我必须说实话,这既令人惊讶又荒谬。 TryXXX方法的重点是它们不应该抛出异常。我非常失望。

答案 1 :(得分:5)

除了Brian Gideon在 Update 之后提到的警告之外,他的解决方案还存在这些性能问题:

    队列(queue.Contains(item))上的
  • O(n)操作会对队列增长产生严重影响
  • 锁定限制并发(他提到了)

以下代码通过

改进了Brian的解决方案
  • 使用哈希集来执行 O(1)查找
  • 合并System.Collections.Concurrent命名空间
  • 中的2个数据结构

N.B。由于没有ConcurrentHashSet,我使用ConcurrentDictionary,忽略了这些值。

在极少数情况下,幸运的是,可以简单地从多个更简单的并发数据结构中构建更复杂的并发数据结构,而无需添加锁。这两个并发数据结构的操作顺序非常重要。

public class NoDuplicatesConcurrentQueue<T> : IProducerConsumerCollection<T>
{
    private readonly ConcurrentDictionary<T, bool> existingElements = new ConcurrentDictionary<T, bool>();
    private readonly ConcurrentQueue<T> queue = new ConcurrentQueue<T>();

    public bool TryAdd(T item)
    {
        if (existingElements.TryAdd(item, false))
        {
            queue.Enqueue(item);
            return true;
        }
        return false;
    }

    public bool TryTake(out T item)
    {
        if (queue.TryDequeue(out item))
        {
            bool _;
            existingElements.TryRemove(item, out _);
            return true;
        }
        return false;
    }
    ...
}

N.B。查看此问题的另一种方法:您希望一个保留插入顺序的集合

答案 2 :(得分:1)

我建议用锁来实现你的操作,这样你就不会以破坏它的方式读取和写入项目,使它们成为原子。例如,使用任何IEnumerable:

object bcLocker = new object();

// ...

lock (bcLocker)
{
    bool foundTheItem = false;
    foreach (someClass nextItem in myBlockingColl)
    {
        if (nextItem.Equals(item))
        {
            foundTheItem = true;
            break;
        }
    }
    if (foundTheItem == false)
    {
        // Add here
    }
}