Подтвердить что ты не робот

Странная "коллекция была изменена после исключения экземпляра счетчика"

Возможно, кто-то может указать мне в правильном направлении, потому что я полностью в тупике.

У меня есть функция, которая просто распечатывает 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 (если это имеет значение). Следует отметить, что распечатка достаточно частая, что вывод консоли замедляет работу программы в целом.

Я полностью в тупике, кто-нибудь еще там сталкивается с этим?

4b9b3361

Ответ 1

Я подозреваю, что место для поиска будет находиться в любых местах, где вы манипулируете списком, т.е. вставляете/удаляете/переустанавливаете элементы. Мое подозрение в том, что где-нибудь будет обработчик обратного вызова/четного сообщения, который запускается асинхронно (возможно, как часть красок и т.д.), И который редактирует список - по существу, это проблема как условие гонки.

Чтобы проверить, действительно ли это так, поместите вывод отладки/трассировки вокруг мест, которые манипулируют списком, и посмотрите, будет ли он когда-либо (и, в частности, незадолго до исключения) запускать код манипуляции одновременно с вашим вывод консоли:

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
  • проверить наличие и вставить/удалить
  • и т.д.

(то есть не отдельные/дискретные операции, а единицы работы)

Ответ 2

Вместо foreach, я использую while( collection.count >0), затем используйте collection[i].

Ответ 3

Я не знаю, относится ли это к OP, но у меня была такая же ошибка и нашла этот поток во время поиска в google. Я смог решить это, добавив разрыв после удаления элемента в цикле.

foreach( Weapon activeWeapon in activeWeapons ){

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

Если вы оставите перерыв, вы получите сообщение об ошибке "InvalidOperationException: Collection был изменен после того, как был указан экземпляр счетчика".

Ответ 4

Использование Break может быть способом, но это может повлиять на вашу серию операций. Что я делаю в этом случае, просто конвертирую foreach в традиционный цикл for

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

Это работает без проблем.