13

也许有人可以指出我正确的方向,因为我完全被这件事难住了。

我有一个函数可以简单地打印出类的 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 环境中(如果重要的话)。应该注意的是,打印输出的频率足够高,以至于控制台输出会降低整个程序的速度。

我完全被难住了,还有其他人遇到过这个吗?

4

4 回答 4

14

我怀疑开始查找的地方将是您操作列表的任何地方 - 即插入/删除/重新分配项目。我怀疑在某个地方会有一个回调/偶数处理程序被异步触发(可能作为 XNA绘制等循环的一部分),并且正在编辑列表 - 本质上是导致这个问题作为竞争条件。

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

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

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

一种答案是同步访问;即在所有编辑列表的地方,使用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
  • 检查存在插入/删除
  • ETC

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

于 2009-05-10T08:41:24.343 回答
4

而不是foreach,我使用while( collection.count >0)然后使用collection[i]

于 2010-06-24T12:37:26.887 回答
3

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

foreach( Weapon activeWeapon in activeWeapons ){

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

如果你忽略了中断,你会得到错误“InvalidOperationException: Collection was modified after the enumerator was instantiated。”

于 2010-08-19T20:38:17.833 回答
0

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

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

这没有任何问题。

于 2015-11-19T08:27:07.380 回答