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

本文关键字:安全 集合 修改 线程 并发 字典 | 更新日期: 2023-09-27 18:00:09

最近我在使用通用字典时遇到以下异常

出现InvalidOperationException。一个集合已修改

我意识到这个错误主要是由于我使用的静态字典上的线程安全问题。

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

  1. 方法A使用foreach遍历字典并返回一个值
  2. 方法B将数据添加到字典中
  3. 方法C更改字典中键的值

有时,在遍历字典时,还会添加数据,这就是导致此问题的原因。我在代码的foreach部分不断地得到这个异常,在那里我迭代字典的内容。为了解决这个问题,我用ConcurrentDictionary替换了通用字典,下面是我所做的详细操作。

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

对于方法B(它为字典添加了一个新密钥),我用TryAdd 替换了.Add

对于方法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会通知我这是发生错误的地方。我使用的代码类似于下面的

    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
    }

这些更改会解决我遇到的异常吗。

编辑:

它在该页面上指出,方法GetEnumerator允许您在写入的同时遍历元素(尽管它可能已经过时)。这和使用foreach不一样吗?

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

对于元素的修改,一种选择是使用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。

您可以通过调用测试显式更新dictionaryLength来扩展它。Count(),还可以使用一个额外的列表,其中包含你已经修改过的键的列表等等。如果有丢失任何键的危险,这实际上取决于你在做什么以及你的需求。

您可以使用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.

请注意,我还展示了一个示例,说明如何在获得初始密钥列表和完成迭代之间发现是否添加了任何新密钥,以便循环并处理新密钥。

希望这些选项中的一个会适合(或者你甚至可能想混合和匹配——例如,键上的循环,而不是长度)——正如我所说,这与你想做的事情一样重要。

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

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

此外,我认为从线程安全的角度来看,更新Value属性是不对的,请重构代码以使用TryUpdate()