Странная "коллекция была изменена после создания экземпляра перечислителя" исключение
возможно, кто-то может указать мне в правильном направлении, потому что я полностью в тупике.
у меня есть функция, которая просто распечатывает 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 ответов
Я подозреваю, что место для начала поиска будет в любых местах, где вы манипулируете списком, т. е. вставляете/удаляете/повторно назначаете элементы. Мое подозрение в том, что где-то будет обратный вызов/даже-обработчик, который запускается асинхронно (возможно, как часть XNA краски etc loops), и который редактирует список - по существу, вызывая эту проблему как условие гонки.
чтобы проверить, так ли это, поместите некоторые выходные данные отладки/трассировки вокруг мест, которые манипулируйте списком и посмотрите, запускает ли он когда-либо (и, в частности, непосредственно перед исключением) код манипуляции одновременно с выводом консоли:
private void SomeCallback()
{
Console.WriteLine("---Adding foo"); // temp investigation code; remove
components.AddLast(foo);
Console.WriteLine("---Added foo"); // temp investigation code; remove
}
к сожалению, такие вещи часто являются болью для отладки, так как изменение кода для его исследования часто меняет проблему (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
- проверять наличие и вставить/удалить
- etc
(т. е. не отдельные/дискретные операции, а единицы работы)
Я не знаю, имеет ли это отношение к 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: коллекция была изменена после создания экземпляра перечислителя."
использование Break может быть способом, но это может повлиять на вашу серию операций. Что я делаю в этом случае, просто преобразую foreach в традиционный for loop
for(i=0;i<List.count;i++)
{
List.Remove();
i--;
}
это работает без каких-либо проблем.