问题 奇怪的“集合在枚举器实例化后被修改”异常


也许有人可以指出我正确的方向,因为我完全被这个困扰了。

我有一个函数,只需打印出一个LinkedList类:

    LinkedList<Component> components = new LinkedList<Component>();
    ...
    private void PrintComponentList()
    {
        Console.WriteLine("---Component List: " + components.Count + " entries---");
        foreach (Component c in components)
        {
            Console.WriteLine(c);
        }
        Console.WriteLine("------");
    }

Component 对象实际上有一个自定义 ToString() 这样打电话:

    int Id;
    ...
    public override String ToString()
    {
        return GetType() + ": " + Id;
    }

这个函数通常可以正常工作 - 但是我遇到的问题是当它在列表中构建大约30个左右的条目时, PrintcomplentList  foreach 声明回来了 InvalidOperationException: Collection was modified after the enumerator was instantiated.

现在您可以看到我没有修改for循环中的代码,并且我没有显式创建任何线程,尽管这是在XNA环境中(如果它很重要)。应该注意的是,打印输出频繁,控制台输出整体上减慢了程序的速度。

我完全难过了,有没有其他人遇到这个?


4132
2018-05-10 06:57


起源

这听起来非常奇怪。你可以发短信但是 完成 程序,所以我们可以尝试重现它? - Jon Skeet
我会看到我能做什么。 - cyberconte
我没有得到一个小程序来复制行为,所以我将看到在那里获得一个线程安全的LinkedList实现,看看它是否捕获任何东西 - cyberconte


答案:


我怀疑开始寻找的地方将在你操纵列表的任何地方 - 即插入/删除/重新分配项目。我怀疑是否会有一个异步触发的回调/偶数处理程序(可能作为XNA的一部分) 涂料 等循环),正在编辑列表 - 实际上导致此问题作为竞争条件。

要检查是否是这种情况,请在操作列表的位置周围放置一些调试/跟踪输出,并查看它是否曾经(特别是在异常之前)在控制台输出的同时运行操作代码:

private void SomeCallback()
{
   Console.WriteLine("---Adding foo"); // temp investigation code; remove
   components.AddLast(foo);
   Console.WriteLine("---Added foo"); // temp investigation code; remove
}

不幸的是,这样的事情通常很难调试,因为更改代码来调查它通常会改变问题(a Heisenbug)。

一个答案是同步访问;即在... 所有 编辑列表的地方,使用a lock 围绕完整的操作:

LinkedList<Component> components = new LinkedList<Component>();
readonly object syncLock = new object();
...
private void PrintComponentList()
{
    lock(syncLock)
    { // take lock before first use (.Count), covering the foreach
        Console.WriteLine("---Component List: " + components.Count
              + " entries---");
        foreach (Component c in components)
        {
           Console.WriteLine(c);
        }
        Console.WriteLine("------");
    } // release lock
}

并在你的回调(或其他)

private void SomeCallback()
{
   lock(syncLock)
   {
       components.AddLast(foo);
   }
}

特别是,“完整操作”可能包括:

  • 检查计数   foreach/for
  • 检查是否存在  插入/删除
  • 等等

(即不是个人/离散操作 - 而是工作单位)


10
2018-05-10 08:41



另请注意,在编写跟踪线时记录线程ID可以帮助您确定是否存在竞争线程,从而确定是否存在竞争的可能性。 - Marc Gravell♦


代替 foreach, 我用 while( collection.count >0) 然后用 collection[i]


4
2018-06-24 12:37





我不知道这是否与OP有关,但我有同样的错误,并在谷歌搜索期间找到了这个帖子。我能够通过在删除循环中的元素后添加一个中断来解决它。

foreach( Weapon activeWeapon in activeWeapons ){

            if (activeWeapon.position.Z < activeWeapon.range)
            {
                activeWeapons.Remove(activeWeapon);
                break; // Fixes error
            }
            else
            {
                activeWeapon.position += activeWeapon.velocity;
            }
        }
    }

如果省略中断,则会收到错误“InvalidOperationException:在实例化枚举数后修改了集合”。


2
2017-08-19 20:38



这引入了另一个错误 - 在第一个超出范围的武器之后,你的任何武器都不会修改它们的位置!我在过去通过创建第二个清单从第一个列表中删除,在一个foreach中填充,然后在第二个列表上执行foreach并从第一个列表中删除其中的每个项目来处理此问题。 - twon33


使用Break可能是一种方式,但它可能会影响您的一系列操作。 在这种情况下,我只是将foreach转换为传统的for循环

for(i=0;i<List.count;i++)
{
List.Remove();
i--;
}

这没有任何问题。


0
2017-11-19 08:27



这可能有效,但在这种情况下,迭代是在集合的对象上。 foreach(collection.keys中的var obj)... - Futureproof