使用下面列出的普通字典代码,我得到了异常(exception)
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,这可以正常工作。
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 会给你更好的性能。
不同行为的原因是:GetEnumerator() 方法的不同实现。
现在我将解释使用 Dictionary 出现异常的原因以及使用 ConcurrentDictionary 没有出现异常的原因。
foreach 语句是以下内容的语法糖:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
字典中的“GetEnumerator()”返回名为“Enumerator”的结构体的新实例
这个结构实现了:IEnumerator >KeyValuePair>TKey,TValue>>, IDictionaryEnumerator 和他的 C'tor 看起来像:
internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
this.getEnumeratorRetType = getEnumeratorRetType;
current = new KeyValuePair<TKey, TValue>();
}
"Enumerator"中 MoveNext() 的实现首先验证源字典没有被修改:
bool moveNext(){
if (version != dictionary.version) {
throw new InvalidOperationException....
}
//the itarate over...
}
ConcurrentDictionary 中的“GetEnumerator()”实现了一种不同的方式:
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;
}
}
}
在这个实现中,有一种称为“懒惰评估”的技术,返回语句将返回值。
当消费者调用 MoveNext() 时,您将返回“current = current.m_next;”
因此,GetEnumerator() 中没有“未更改”验证。
如果您想避免“字典编辑”出现异常,则:
1.迭代到要删除的元素
2.删除元素
3. 在 MoveNext() 调用之前中断
在你的例子中:
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/en-us/library/dd287131(v=vs.110).aspx
关于c# - 在 Dictionary 和 ConcurrentDictionary 之间修改集合时的不同行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28480150/