I\'ve run into an interesting issue. Knowing that the ConcurrentDictionary
is safely enumerable while being modified, with the (in my case)
Let's answer the broad over-shadowing question here for all the concurrent types:
If you split up an operation that deals with the internals in multiple steps, where all the steps must "be in sync", then yes, definitively you will get crashes and odd results due to thread synchronization.
So if using .ToList()
will first ask for .Count
, then size an array, and then use foreach
to grab the values and place in the list, then yes, definitively you will have the chance of the two parts getting a different number of elements.
To be honest I wish some of those concurrent types did not try to pretend they were normal collections by implementing a lot of those interfaces but alas, that's how it is.
Can you fix your code, now that you know about the issue?
Yes you can, you must take a look at the type documentation and see if it provides any form of snapshotting mechanism that isn't prone to the above mentioned problems.
Turns out ConcurrentDictionary<TKey, TValue>
implements .ToArray()
, which is documented with:
A new array containing a snapshot of key and value pairs copied from the System.Collections.Concurrent.ConcurrentDictionary.
(my emphasis)
How is .ToArray()
currently implemented?
Using locks, see line 697.
So if you feel locking the entire dictionary to get a snapshot is too costly I would question the act of grabbing a snapshot of its contents to begin with.
Additionally, the .GetEnumerator()
method follows some of the same rules, from the documentation:
The enumerator returned from the dictionary is safe to use concurrently with reads and writes to the dictionary, however it does not represent a moment-in-time snapshot of the dictionary. The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called.
(again, my emhpasis)
So while .GetEnumerator()
won't crash, it may not produce the results you want.
Depending on timing, neither may .ToArray()
, so it all depends.