I've run into an interesting issue. Knowing that the ConcurrentDictionary<TKey, TValue>
is safely enumerable while being modified, with the (in my case) unwanted side-effect of iterating over elements that may disappear or appear multiple times, I decided to create a snapshot myself, using ToList()
. Since ConcurrentDictionary<TKey, TValue>
also implements ICollection<KeyValuePair<TKey, TValue>>
, this causes the List(IEnumerable<T> collection)
to be used, which in turn creates an array in the current size of the dictionary using the current item Count
, then attempts to copy over the items using ICollection<T>.CopyTo(T[] array, int arrayIndex)
, calling into its ConcurrentDictionary<TKey, TValue>
implementation, and finally throwing an ArgumentException
if elements are added to the dictionary in the meantime.
Locking all over would kill the point of using the collection as it is, so my options seem to be to either keep catching the exception and retrying (which is definitely not the right answer to the problem), or to implement my own version of ToList()
specialized for this issue (but then again, simply growing a list then possibly trimming it to the right size for a few elements seems like an overkill, and using a LinkedList would decrease indexing performance).
In addition, it seems like adding certain LINQ methods that create some sort of a buffer in the background (such as OrderBy
) do seem to mend the problem at the cost of performance, but the bare ToList()
obviously does not, and it's not worth "augmenting" it with another method when no additional functionality is needed.
Could this be an issue with any concurrent collection?
What would be a reasonable workaround to keep performance hits to the minimum while creating such a snapshot? (Preferably at the end of some LINQ magic.)
Edit:
After looking into it I can confirm, ToArray()
(to think that I just passed by it yesterday) really does solve the snapshot problem as long as it's just that, a simple snapshot, it does not help when additional functionality is required before taking said snapshot (such as filtering, sorting), and a list/array is still needed at the end. (In this case, an additional call is required, creating the new collection all over again.)
I failed to point out that the snapshot may or may not need to go through these modifications, so it should be taken at the end, preferably, so I'd add this to the questions.
(Also, if anyone has a better idea for a title, do tell.)
See Question&Answers more detail:
os