4

What is the best way to check if an item is existed in the BlockingCollection before trying to add a new one? Basically I do not want duplicates to be added to the BlockingCollection.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Time Machine
  • 157
  • 1
  • 14

2 Answers2

6

You will have to implement your own IProducerConsumerCollection<T> that behaves like a set (e.g. no duplicates allowed). Here is a simplistic version that use a critical section (C# lock) to make it thread-safe. For high concurrency scenarios you may be able to improve performance by using a class like SpinWait the same way as ConcurrentQueue<T> does.

public class ProducerConsumerSet<T> : IProducerConsumerCollection<T> {

  readonly object gate = new object();

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

  readonly HashSet<T> hashSet = new HashSet<T>();

  public void CopyTo(T[] array, int index) {
    if (array == null)
      throw new ArgumentNullException("array");
    if (index < 0)
      throw new ArgumentOutOfRangeException("index");
    lock (gate)
      queue.CopyTo(array, index);
  }

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

  public bool TryTake(out T item) {
    lock (gate) {
      if (queue.Count == 0) {
        item = default(T);
        return false;
      }
      item = queue.Dequeue();
      hashSet.Remove(item);
      return true;
    }
  }

  public T[] ToArray() {
    lock (gate)
      return queue.ToArray();
  }

  public void CopyTo(Array array, int index) {
    if (array == null)
      throw new ArgumentNullException("array");
    lock (gate)
      ((ICollection) queue).CopyTo(array, index);
  }

  public int Count {
    get { return queue.Count; }
  }

  public object SyncRoot {
    get { return gate; }
  }

  public bool IsSynchronized {
    get { return true; }
  }

  public IEnumerator<T> GetEnumerator() {
    List<T> list = null;
    lock (gate)
      list = queue.ToList();
    return list.GetEnumerator();
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator();
  }

}

If required you can elaborate on this class to customize equality by supplying an optional IEqualityComparer<T> that then is used to initialize the HashSet<T>.

The IProducerConsumerCollection<T>.Add methods returns false when there is an attempt to insert a duplicate item. This results in an InvalidOperationException thrown by the BlockingCollection<T>.Add method so you will probably have to wrap the code to add an item into something like this:

bool AddItem<T>(BlockingCollection<T> blockingCollection, T item) {
  try {
    blockingCollection.Add(item);
    return true;
  }
  catch (InvalidOperationException) {
    return false;
  }
}

Note that if you add items to a collection that has been completed you will also get an InvalidOperationException and you will have to examine the exception message to determine the underlying reason for the exception.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 1
    I found an issue with this approach, the blocking collection does not reach the upper bound if duplicate items are added by producers that result in InvalidOperationException – Aashish Upadhyay Jun 02 '18 at 15:05
  • @AashishUpadhyay the issue is caused by [a bug](https://github.com/dotnet/runtime/issues/69816) in the `BlockingCollection` class, that is going to be fixed in .NET 8. For now the workaround is to throw an exception instead of returning `false` from the `TryAdd`. I have posted an `IProducerConsumerCollection` implementation that incorporates this workaround [here](https://stackoverflow.com/questions/7652669/concurrent-collections-and-unique-elements/75354636#75354636 "Concurrent collections and unique elements"). – Theodor Zoulias Feb 05 '23 at 22:56
-3

Use TryAdd(data) Method. You can also pass in a timespan object or and int indicating a timeout period. Returns true or false. Note that if the underlying collection type cannot handle duplicates and the data you are trying to add IS a duplicate then an InvalidOperationException is raised.

gklots
  • 1,478
  • 2
  • 10
  • 8
  • 2
    All underlying (IProdCons) classes accept duplicates, so I don't think this will help by itself. – H H Mar 02 '16 at 11:40