在Dictionary和ConcurrentDictionary之间修改集合时的不同行为 [英] Different behaviour when collection modified between Dictionary and ConcurrentDictionary

查看:375
本文介绍了在Dictionary和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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆