收到"错误:收集被修改;枚举操作可能无法执行"在多线程应用程序中

时间:2014-06-19 16:26:44

标签: c# multithreading collections enumerator

目前,我正在编写一个定期消耗单件集合的应用程序:

            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...
}

1 个答案:

答案 0 :(得分:1)

从技术上讲,这可能有用,但我认为它仍然过于做作。为什么不:

    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块中。