11

Simple question Assume that i have a ConcurrentDictionary

I use TryAdd and ContainsKey methods

Now assume that from 100 threads i started to process stuff. Assume that when 3 threads while adding a new key with TryAdd method another 3 threads asking whether key exists or not with ContainsKey method

Do ContainsKey wait those 3 threads adding process before returning me result ?

Or they are not synched i mean that one of those 3 threads could be adding the key i am asking with ContainsKey method however since the process is not done yet the answer i was gonna get would be false

Ty very much for answers C# WPF .net 4.5 Latest

Liam
  • 27,717
  • 28
  • 128
  • 190
Furkan Gözükara
  • 22,964
  • 77
  • 205
  • 342

1 Answers1

12

"No" (see Sam's comment), furthermore there is no atomic guard established by ContainsKey across other access or method calls to the ConcurrentDictionary.

That is, the following code is broken

// There is no guarantee the ContainsKey will run before/after
// different methods (eg. TryAdd) or that the ContainsKey and another
// method invoked later (eg. Add) will be executed as an atomic unit.
if (!cd.ContainsKey("x")) {
  cd.Add("x", y);
}

and the Try* methods should be used consistently instead

cd.TryAdd("x", y);

If further synchronization (or atomicity) needs to be guaranteed past the specialized concurrent methods then a larger monitor/lock context should be established.

user2864740
  • 60,010
  • 15
  • 145
  • 220
  • 3
    -1: I interpreted the question to in bold to read "Does `ContainsKey` contain a synchronization call that will cause it to wait until the calls to `TryAdd` complete before returning its answer?" The answer to this question is "no". Concurrent collections are very different from collections which use locking for synchronization; they avoid locking wherever possible, typically by employing [lock-free or wait-free algorithms](http://rethinkdb.com/blog/lock-free-vs-wait-free-concurrency/). – Sam Harwell Aug 20 '14 at 03:14
  • 3
    @280Z28 A good observation. I've updated the initial wording with respect to the comment. Also, as supporting reasoning, `ContainsKey` uses TryGetValue internally (ref. [ConcurrentDictionary.cs from grepcode](http://www.dotnetframework.org/default.aspx/4@0/4@0/untmp/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/Collections/Concurrent/ConcurrentDictionary@cs/1305376/ConcurrentDictionary@cs)). – user2864740 Aug 20 '14 at 03:19
  • 2
    I removed my -1 vote, but left the comment in place since it's referenced. Here's a better link: http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs#382 – Sam Harwell Aug 20 '14 at 03:21
  • Even TryAdd can fail if you're counting on containsKey to tell you if the key exists from a potentially previously deleted key. The concurrentdictionary gets into a state where it thinks the key doesn't exist, and whether or not it does, it can't add the same key. This seems horribly broken. – nixkuroi Jun 22 '17 at 23:33
  • @nixkuroi Switching `TryAdd` for `Add` in the "broken" case is broken for the exact *same* reason: there is a race condition between the two method calls. The concurrent methods are only *individually* atomic. Failure to use the API correctly will result in code that is *not* thread-safe and results in behavior which, at first, may appear to be "horribly broken". – user2864740 Jun 23 '17 at 00:24
  • 1
    @nixkuroi However, at no time does the ConcurrentDictionary's internal state become corrupt. The result of TryAdd should be checked to ensure if the particular key was added; any larger atomic contexts must be guarded by other means such as locks.) – user2864740 Jun 23 '17 at 00:30