DCSIMG
Why doesn't ConcurrentBag implement ICollection<T>? - Doron's .NET Space

Why doesn't ConcurrentBag implement ICollection<T>?

Hopefully you’ve encountered by now the System.Collections.Concurrent namespace. It’s new to .NET 4, and it has many useful data structures which are optimized for multi-threaded usage. ConcurrentBag, for instance, is an unordered collection of objects, which multiple threads can add and remove objects from at the same time.

Recently I needed to test that some piece of code was thread-safe. To do that, I wanted to run it once synchronously, and once asynchronously. It looked something like that:

   1: private static IEnumerable<ComputationResult> ComputeSynchronous(IEnumerable<string> inputs)
   2: {
   3:     Console.WriteLine("Starting synchrnous correction.");            
   4:     var results = new ConcurrentBag<ComputationResult>();
   5:     foreach (var input in inputs)
   6:     {       
   7:         //Some more printouts and work here
   8:         var result = computation.Compute(input);                
   9:         //Some more printouts and work here
  10:         results.Add(result);                
  11:     }
  12:     return results;
  13: }
  14:  
  15: private static IEnumerable<ComputationResult> ComputeAsynchronous(IEnumerable<string> inputs)
  16: {
  17:     Console.WriteLine("Starting asynchrnous correction.");
  18:     var results = new ConcurrentBag<ComputationResult>();
  19:     Parallel.ForEach(inputs, input =>
  20:         {                    
  21:             //Some more printouts and work here
  22:             var result = computation.Compute(input);
  23:             //Some more printouts and work here
  24:             results.Add(result);                    
  25:         });
  26:     return results.ToList();
  27: }

And so, the first method computes stuff in one thread, and the second does the same computation on the same inputs, but inside a parallel loop. This is all nice and simple, but I was a little bothered by the fact the the code inside the loop in the two methods repeats itself. The logical conclusion would be to extract a method, of course. This should look something like this:

   1: private static void ComputeAndAdd(string input, ICollection<ComputationResult> results)
   2: {
   3:     //Some more printouts and work here
   4:     var result = computation.Compute(input);
   5:     //Some more printouts and work here
   6:     results.Add(result);                        
   7: }

Only, you can’t write this method. If you try to pass it a ConcurrentBag<T> it will not compile, since that class doesn’t implement ICollection<T> nor IList<T>. This seemed strange. You can add and remove items from this collection. You can iterate it, why doesn’t it implement a more robust interface (you only get IEnumerable<T> and the non-generic ICollection, which doesn’t have an Add method). Baffled, I went and did the logical thing: asked in StackOverflow.

At first there was a suggestion to use SynchronizedCollection<T>, which does implement the interface I needed. But this is and older class that exists since .NET 2.0, and it isn’t as optimized as the newer ConcurrentBag. Also, it doesn’t really answer the question.

Later, user Rick Sladkey provided a better answer, and I’ll let him do the talking here:

A List<T> is not concurrent and so it can implement ICollection<T> which gives you the pair of methods Contains and Add. If Contains returns false you can safely call Add knowing it will succeed.

A ConcurrentBag<T> is concurrent and so it cannot implement ICollection<T> because the answer Contains returns might be invalid by the time you call Add. Instead it implements IProducerConsumerCollection<T> which provides the single method TryAdd that does the work of both Contains and Add.

So even though it seems That ConcurrentBag<T> implements the contract of ICollection<T>, it really doesn’t. There’s an underlying contract, that isn’t expressed in the interface’s methods, which ConcurrentBag<T> simply can’t implement. Rick then goes on and suggests using delegates to solve the above problem, a decent solution.

Published Wednesday, May 04, 2011 9:35 PM by dorony

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: