使用并发字典 - 线程安全集合修改

时间:2013-02-20 05:02:09

标签: c# concurrency

最近,当使用通用字典时,我遇到了以下异常

  

发生了InvalidOperationException。修改了一个集合

我意识到这个错误主要是因为我正在使用的静态字典上存在线程安全问题。

一点背景:我目前有一个应用程序有3种与此问题相关的不同方法。

  1. 方法A使用foreach遍历字典并返回值。
  2. 方法B将数据添加到字典中。
  3. 方法C更改字典中键的值。
  4. 有时在迭代字典时,也会添加数据,这是导致此问题的原因。我一直在我的代码的foreach部分中获得此异常,我在其中迭代字典的内容。为了解决这个问题,我用ConcurrentDictionary替换了通用字典,以下是我所做的细节。

    目标:我的主要目标是完全删除异常

    对于方法B (向字典中添加新密钥)我将.Add替换为TryAdd

    对于方法C (更新字典的值)我没有做任何更改。代码的草图如下:

      static public int ChangeContent(int para)
      {
          foreach (KeyValuePair<string, CustObject> pair in static_container)
          {
                 if (pair.Value.propA != para ) //Pending cancel 
                 {
                    pair.Value.data_id = prim_id;    //I am updating the content
                    return 0;
    
                 }
          }
         return -2;
      }
    

    对于方法A - 我只是遍历字典,这就是运行代码停止的地方(在调试模式下),Visual Studio告诉我这是发生错误的地方。代码I我正在使用类似于以下

        static public CustObject RetrieveOrderDetails(int para)
        {
                foreach (KeyValuePair<string, CustObject> pair in static_container)
                {                   
                    if (pair.Value.cust_id.Equals(symbol))
                    {
                        if (pair.Value.OrderStatus != para) 
                        {
                           return pair.Value; //Found
                        }
                    }
                }
                return null; //Not found
        }
    

    这些更改是否会解决我得到的异常。

    修改

    它在this page上声明方法GetEnumerator允许您与写入并行遍历元素(尽管它可能已过时)。和使用foreach一样吗?

2 个答案:

答案 0 :(得分:3)

对于元素的修改,一个选项是使用for循环手动迭代字典,例如:

Dictionary<string, string> test = new Dictionary<string, string>();
int dictionaryLength = test.Count();

for (int i = 0; i < dictionaryLength; i++)
{
    test[test.ElementAt(i).Key] = "Some new content";
}

但是,如果你还要添加到Dictionary中,那么你必须适当地增加dictionaryLength(或者如果你移动元素就减少它)。

根据您的具体操作,以及订单是否重要,您可能希望使用SortedDictionary。

你可以通过在每次迭代时通过调用test.Count()显式更新dictionaryLength来扩展它,并且还使用包含你已经修改过的键列表的附加列表等等,如果有丢失的危险的话等等任何,这真的取决于你在做什么以及你需要什么。

您可以使用test.Keys.ToList()进一步获取密钥列表,该选项的工作方式如下:

Dictionary<string, string> test = new Dictionary<string, string>();
List<string> keys = test.Keys.ToList();
foreach (string key in keys)
{
    test[key] = "Some new content";
}

IEnumerable<string> newKeys = test.Keys.ToList().Except(keys);

if(newKeys.Count() > 0)
    // Do it again or whatever.

请注意,我还展示了一个示例,说明如何在获取初始键列表和完成迭代之间添加任何新键,然后循环并处理新键。

希望这些选项中的一个适合(或者你甚至可能想要在键上混合和匹配 - 例如更新你去的时候而不是长度) - 正如我所说,它与你究竟是什么一样多我试图做任何事情。

答案 1 :(得分:1)

在执行foreach()之前尝试将容器复制到新实例

var unboundContainer = static_container.ToList();
foreach (KeyValuePair<string, CustObject> pair in unboundContainer)

此外,我认为从线程安全角度更新Value属性是不正确的,重构代码以使用TryUpdate()代替。