接收"错误:收集已被修改;枚举操作不能执行.在多线程应用程序中

本文关键字:不能 操作 枚举 执行 多线程 应用程序 修改 错误 quot 接收 | 更新日期: 2023-09-27 18:09:17

目前,我正在编写一个定期抽取单例集合的应用程序:

            lock (_lockItemsReceivedObject)
            {
                DateTime commitTime = DateTime.Now;
                while (ItemsReceivedInstance.Count > 0)
                {
                    ProcessInfo(commitTime);
                }
            }

这里是ProcessInfo:

   private void ProcessInfo(DateTime commitTime)
    {
        Dictionary<Int32, Item>.Enumerator enumerator = 
            ItemsReceivedInstance.GetEnumerator();
        if (enumerator.MoveNext())
        {
            Item item = enumerator.Current.Value;
            // put item into persistent storage and perform other processing...
            ItemsReceivedInstance.Remove(enumerator.Current.Key);
        }
    }

下面是关于这个异常的更多细节:

Error: Collection was modified; enumeration operation may not execute. at  System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()

在程序的其他地方,其他线程正在接收项并将它们放入单例ItemsReceivedInstance集合中。然而,对我来说没有意义的是,由于我正在使用锁,ItemsReceivedInstance集合应该不能被修改,直到它在进程退出临界区时被清空,所以为什么我收到这个异常?有人有什么建议吗?TIA。

更新:

感谢CodeWeed和Wayne的评论。这是一种可以接受的修改集合的方式吗?

    {
        ConcurrentDictionary<Int32, Item>.Enumerator enumerator = 
            ItemsReceivedInstance.GetEnumerator();
        if (enumerator.MoveNext())
        {
            Item item = enumerator.Current.Value;
            // put item into persistent storage and perform other processing...
            var itemToRemove = enumerator.Current.Key;
            enumerator.Dispose();
            ItemsReceivedInstance.Remove(itemToRemove);
        }
    }
更新2:

感谢CodeWeed和Wayne,以及所有考虑过这个问题的人。foreach循环枚举器允许脏读取,因此为了获取字典的快照,我使用ToArray()(参见http://geekswithblogs.net/BlackRabbitCoder/archive/2011/02/17/c.net-little-wonders-the-concurrentdictionary.aspx),这就是我如何修改代码的:

DateTime commitTime = DateTime.Now; 
foreach (KeyValuePair<Int32, Item> kvp in ItemsReceivedInstance.ToArray())
{
    ProcessInfo(commitTime, kvp.Value);
}
...
private static void ProcessInfo(DateTime commitTime, Item item)
{
    // put item into persistent storage and perform other processing...
}

接收"错误:收集已被修改;枚举操作不能执行.在多线程应用程序中

从技术上讲,这可能可行,但我认为它仍然太做作了。为什么不:

    DateTime commitTime = DateTime.Now; 
    foreach (var kvp in ItemsReceivedInstance)
    {
        ProcessInfo(commitTime, kvp);
    }
    ItemsReceivedInstance.Clear();
    ...
    private static void ProcessInfo(DateTime commitTime, KeyValuePair<int, Item> kvp)
    {
        // put item into persistent storage and perform other processing...
    }

请注意,这与您最初试图实现的目标之间的细微差异在于ProcessInfo中异常的影响。如果您正在处理异常,并且可能不希望因此重新处理字典中的项,那么您将希望跟踪哪些项已成功处理,并从字典中删除这些项,可能是在finally块中。