BKS*_*BKS 4 c# dictionary enumeration .net-4.0 concurrentdictionary
使用下面列出的普通字典代码,我得到了例外
集合被修改;枚举操作可能无法执行。
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);
}
Run Code Online (Sandbox Code Playgroud)
但是对于 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);
}
Run Code Online (Sandbox Code Playgroud)
为什么会有行为差异?
原因是 Dictionary 和 ConcurrentDictionary 有不同的用途。ConcurrentDictionary - 应该处理并发问题(从不同线程编辑),而 Dictionary 会给你更好的性能。
不同行为的原因是:GetEnumerator() 方法的不同实现。
现在我将解释使用 Dictionary 出现异常的原因以及使用 ConcurrentDictionary 没有出现异常的原因。
foreach 语句是以下内容的语法糖:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
Run Code Online (Sandbox Code Playgroud)
字典中的“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>();
}
Run Code Online (Sandbox Code Playgroud)
"Enumerator" 中 MoveNext() 的实现首先验证源字典没有被修改:
bool moveNext(){
if (version != dictionary.version) {
throw new InvalidOperationException....
}
//the itarate over...
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这个实现中,有一种称为“懒惰评估”的技术,返回语句将返回值。当消费者调用 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
}
Run Code Online (Sandbox Code Playgroud)
有关 ConcurrentDictionary 的 GetEnumerator() 的更多信息:https ://msdn.microsoft.com/en-us/library/dd287131( v= vs.110).aspx