With a normal Dictionary code as list below, I get exception that
Collection was modified; enumeration operation may not execute.
Dictionary<int, int> dict2 = new Dictionary<int, int>();
dict2.Add(1, 10);
dict2.Add(2, 20);
dict2.Add(3, 30);
dict2.Add(4, 40);
foreach (var d in dict2)
{
if (dict2.ContainsKey(2))
dict2.Remove(2);
if (dict2.ContainsKey(3))
dict2.Remove(3);
}
However with ConcurrentDictionary, this works fine.
ConcurrentDictionary<int, int> dict1 = new ConcurrentDictionary<int, int>();
dict1.AddOrUpdate(1, 10, (k,v)=> 10);
dict1.AddOrUpdate(2, 20, (k, v) => 20);
dict1.AddOrUpdate(3, 30, (k,v)=> 30);
dict1.AddOrUpdate(4, 40, (k,v)=> 40);
foreach (var d in dict1)
{
int x;
if (dict1.ContainsKey(2))
dict1.TryRemove(2, out x);
if (dict1.ContainsKey(3))
dict1.TryRemove(3, out x);
}
Why there is difference in behaviour?
The reason is Dictionary and ConcurrentDictionary have different Purposes. ConcurrentDictionary - supposed to deals with concurrency issues(editing from different threads) while Dictionary will give you a better performance.
The reason to the different behavior is: different implementation of GetEnumerator() method.
Now I will explain the reason for the exception with Dictionary and the reason you do not get exception with ConcurrentDictionary.
The foreach statement is syntactic sugar for something like:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
The "GetEnumerator()" in Dictionary returns new instance of struct named: "Enumerator"
This structure implements: IEnumerator >KeyValuePair>TKey,TValue>>, IDictionaryEnumerator and his C'tor looks like:
internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
this.getEnumeratorRetType = getEnumeratorRetType;
current = new KeyValuePair<TKey, TValue>();
}
The implementation of MoveNext() inside "Enumerator" verify first that the source Dictionary wasn't modified:
bool moveNext(){
if (version != dictionary.version) {
throw new InvalidOperationException....
}
//the itarate over...
}
The "GetEnumerator()" in ConcurrentDictionary implemented a way different:
IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(){
Node[] buckets = m_tables.m_buckets;
for (int i = 0; i < buckets.Length; i++)
{
Node current = Volatile.Read<Node>(ref buckets[i]);
while (current != null)
{
yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
current = current.m_next;
}
}
}
In this implementation there is a technic called "lazy evaluation" the return statement will return the value. When the consumer calls MoveNext() then you will return to "current = current.m_next;" So, there is no "not change" verification inside GetEnumerator().
if you want to avoid exception with the "Dictionary editing" then: 1. iterate to the element you want to remove 2. remove the element 3. break before MoveNext() called
In your example:
foreach (var d in dict2)
{
if (dict2.ContainsKey(1))
dict2.Remove(1);
if (dict2.ContainsKey(3))
dict2.Remove(3);
break; // will prevent from exception
}
for more information about the GetEnumerator() of ConcurrentDictionary: https://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With