Странная "коллекция была изменена после исключения экземпляра счетчика"
Возможно, кто-то может указать мне в правильном направлении, потому что я полностью в тупике.
У меня есть функция, которая просто распечатывает 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 (если это имеет значение). Следует отметить, что распечатка достаточно частая, что вывод консоли замедляет работу программы в целом.
Я полностью в тупике, кто-нибудь еще там сталкивается с этим?
Ответы
Ответ 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--;
}
Это работает без проблем.