4

ConcurrentQueue has TryDequeue method.

Queue has just Dequeue method.

In ConcurrentDictionary there is no Add method, but we have TryAdd instead.

My question is:

What is the diffrence between these concurrent collection methods? Why they are diffrent for concurrent collections?

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
Kamil
  • 13,363
  • 24
  • 88
  • 183
  • since they ensure thread safety access, tryadd may fail if the current thread cant access the dictionary – apomene Jul 15 '16 at 12:48

5 Answers5

5

With a Dictionary<TKey, TValue> it's assumed that you're going to implement your own logic to make sure duplicate keys aren't entered. For example,

if(!myDictionary.ContainsKey(key)) myDictionary.Add(key, value);

But we use Concurrent collections when we have multiple threads going and it's possible that they could both be trying to modify the dictionary at the same time.

If two threads tried to execute the above code at the same time, it's possible that myDictionary.ContainsKey(key) could return false for both threads because they're both checking at the same time and that key hasn't been added yet. Then they both try to add the key, and one fails.

Someone reading that code who doesn't know it's multithreaded could be confused. I checked to make sure that the key wasn't in the dictionary before I added it. So how am I getting an exception?

ConcurrentDictionary.TryAdd solves that by allowing you to "try" to add the key. If it adds the value it returns true. If it doesn't it returns false. But what it won't do is conflict with another TryAdd and throw an exception.

You could do all of that yourself by wrapping the Dictionary in a class and putting lock statements around it to make sure only one thread at a time makes changes. ConcurrentDictionary just does that for you and does it really well. You don't have to see all the details of how it's working - you just use it knowing that multithreading has been accounted for.

Here's a detail to look for when using a class in a multithreaded application. If you go to the documentation for ConcurrentDictionary Class and scroll to the bottom you'll see this:

Thread Safety
All public and protected members of ConcurrentDictionary are thread-safe and may be used concurrently from multiple threads. However, members accessed through one of the interfaces the ConcurrentDictionary implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.

In other words, multiple threads can safely read and modify the collection.

Under Dictionary Class you'll see this:

Thread Safety
A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Multiple threads can read keys, but if multiple threads are going to write then you need to somehow lock the dictionary to make sure only one thread at a time tries to update.

Dictionary<TKey, TValue> exposes a Keys collection and Values collection so you can enumerate the keys and values, but it warns you not to try doing that if another thread is going to be modifying the dictionary. You can't enumerate something while items are being added or removed. If you need to iterate through the keys or values then you have to lock the dictionary to prevent updates during that iteration.

ConcurrentDictionary<TKey, TValue> assumes that there will be multiple threads reading and writing, so it doesn't even expose key or value collections for you to enumerate.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
4

Semantics is different.

Failing of Queue.Dequeue usually indicates a problem with internal application logic, so throwing exceptions is good in such cases.

However failing of ConcurrentQueue.TryDeque is something which might be expected in the regular flow, so avoiding exception and returning a Boolean is a reasonable way to handle it.

ConcurrentQueue<T> handles all synchronization internally. If two threads call TryDequeue at precisely the same moment, neither operation is blocked. When a conflict is detected between two threads, one thread has to try again to retrieve the next element, and the synchronization is handled internally.

(It is common practice in .NET framework to have Try... functions which return Boolean results instead of throwing, see e.g. TryParse methods.)

AlexD
  • 32,156
  • 3
  • 71
  • 65
4

The reason these methods are given a Try semantics is that by design, there is no way to reliably tell that Dequeue or Add operations are going to succeed.

When the queue is not concurrent, you could check if there's anything to dequeue before calling Dequeue method. Similarly, you can check if the key in a non-concurrent Dictionary is present or not. You cannot do the same to concurrent classes, because someone may dequeue your item after you checked for it to be there, but before you get to actually dequeue it. In other words, Try operations let you check the precondition and perform the operation atomically.

An alternative approach would be letting you dequeue or add anyway, and throw an exception when the operation fails, in the way the non-concurrent implementations do. The drawback to this approach is that these exceptional situations in non-concurrent classes are entirely expected in concurrent classes, so using exception handling for them would be wrong.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
3

As these collections are designed to be used concurrently, you can't rely on checking pre-conditions in a sequential manner, you need an atomic operation.

Taking the dictionary as an example, ordinarily you'd be able to write code like this:

if (!dictionary.ContainsKey(key))
{
    dictionary.Add(key, value);
}

In a situation where multiple threads are using the same dictionary, it would be perfectly possible for another thread to have inserted a value with the same key in between you checking ContainsKey and calling Add.

TryAdd solves this as it will either succeed or fail depending on whether the key exists or not.

Charles Mager
  • 25,735
  • 2
  • 35
  • 45
0

From MSDN:

Tries to remove and return the object at the beginning of the concurrent queue.

Returns

true if an element was removed and returned from the beginning of the ConcurrentQueue successfully; otherwise, false.

So if you can remove TryDequeue just reomove and return it, if can't returns false and you know to try again when queeue is free.

BWA
  • 5,672
  • 7
  • 34
  • 45