在Dictionary和ConcurrentDictionary之间修改集合时的不同行为 [英] Different behaviour when collection modified between Dictionary and ConcurrentDictionary
问题描述
使用下面列出的普通词典代码,我得到了
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);
}
但是对于ConcurrentDictionary来说,这可以正常工作.
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);
}
为什么行为上有区别?
推荐答案
原因是Dictionary和ConcurrentDictionary具有不同的用途. ConcurrentDictionary-应该处理并发问题(从不同线程进行编辑),而Dictionary将为您提供更好的性能.
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.
产生不同行为的原因是:GetEnumerator()方法的不同实现.
The reason to the different behavior is: different implementation of GetEnumerator() method.
现在,我将用Dictionary解释发生异常的原因,以及使用ConcurrentDictionary不会获得异常的原因.
Now I will explain the reason for the exception with Dictionary and the reason you do not get exception with ConcurrentDictionary.
foreach语句是类似以下内容的语法糖:
The foreach statement is syntactic sugar for something like:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
字典中的"GetEnumerator()"返回名为"Enumerator"的结构的新实例
The "GetEnumerator()" in Dictionary returns new instance of struct named: "Enumerator"
此结构实现:IEnumerator> KeyValuePair> TKey,TValue >>,IDictionaryEnumerator及其C'tor如下:
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>();
}
枚举器"内部的MoveNext()实现首先验证源字典是否未修改:
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...
}
ConcurrentDictionary中的"GetEnumerator()"实现了另一种方式:
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;
}
}
}
在此实现中,有一种称为惰性评估"的技术,return语句将返回该值. 当使用者调用MoveNext()时,您将返回到"current = current.m_next;". 因此,GetEnumerator()中没有不变"验证.
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().
如果要避免字典编辑"的例外,请执行以下操作: 1.迭代到要删除的元素 2.删除元素 3.在调用MoveNext()之前中断
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
在您的示例中:
foreach (var d in dict2)
{
if (dict2.ContainsKey(1))
dict2.Remove(1);
if (dict2.ContainsKey(3))
dict2.Remove(3);
break; // will prevent from exception
}
有关ConcurrentDictionary的GetEnumerator()的更多信息: https://msdn.microsoft.com/zh-我们/library/dd287131(v=vs.110).aspx
for more information about the GetEnumerator() of ConcurrentDictionary: https://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx
这篇关于在Dictionary和ConcurrentDictionary之间修改集合时的不同行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!