Strange “coleção foi modificada depois que o enumerador foi instanciado” exceção ”
-
20-08-2019 - |
Pergunta
Talvez alguém possa me apontar na direção correta, porque estou completamente perplexo com isso.
Eu tenho uma função que simplesmente imprime uma lista vinculada de aulas:
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("------");
}
o Component
objeto realmente tem um costume ToString()
Ligue como tal:
int Id;
...
public override String ToString()
{
return GetType() + ": " + Id;
}
Essa função normalmente funciona bem - no entanto, eu encontro a questão de que, quando se passa para cerca de 30 entradas na lista, o PrintcomplentList
foreach
A declaração volta com um InvalidOperationException: Collection was modified after the enumerator was instantiated.
Agora, como você pode ver, não estou modificando o código dentro do loop for e não criei explicitamente nenhum threads, embora este esteja dentro de um ambiente XNA (se importante). Deve -se notar que a impressão é frequente o suficiente para que a saída do console esteja desacelerando o programa como um todo.
Estou completamente perplexo, mais alguém por aí encontrou isso?
Solução
Suspeito que o local para começar a procurar estará em qualquer lugar onde você manipule a lista - ou seja, inserir/remover/assinar itens. Minha suspeita é que haverá um retorno de chamada/manipulador em algum lugar que esteja sendo demitido de forma assíncrona (talvez como parte do XNA pintar loops etc.), e que está editando a lista - causando essencialmente esse problema como uma condição de corrida.
Para verificar se é esse o caso, coloque algumas saídas de depuração/rastreamento em torno dos lugares que manipulam a lista e veja se ela já (e, em particular, pouco antes da exceção) executa o código de manipulação ao mesmo tempo que sua saída de console:
private void SomeCallback()
{
Console.WriteLine("---Adding foo"); // temp investigation code; remove
components.AddLast(foo);
Console.WriteLine("---Added foo"); // temp investigation code; remove
}
Infelizmente, essas coisas geralmente são uma dor de depuração, pois mudar o código para investigá -lo geralmente muda o problema (a Heisenbug).
Uma resposta seria sincronizar o acesso; ou seja tudo Os lugares que editam a lista, use um lock
em torno da operação completa:
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
}
E em seu retorno de chamada (ou o que seja)
private void SomeCallback()
{
lock(syncLock)
{
components.AddLast(foo);
}
}
Em particular, uma "operação completa" pode incluir:
- Verifique a contagem e
foreach
/for
- Verifique a existência e Insira/remova
- etc.
(ou seja, não as operações individuais/discretas - mas unidades de trabalho)
Outras dicas
Ao invés de foreach
, Eu uso while( collection.count >0)
Em seguida, use collection[i]
.
Não sei se isso é relevante para o OP, mas tive o mesmo erro e encontrei esse tópico durante uma pesquisa no Google. Consegui resolvê -lo adicionando uma quebra depois de remover um elemento no loop.
foreach( Weapon activeWeapon in activeWeapons ){
if (activeWeapon.position.Z < activeWeapon.range)
{
activeWeapons.Remove(activeWeapon);
break; // Fixes error
}
else
{
activeWeapon.position += activeWeapon.velocity;
}
}
}
Se você deixar de fora o intervalo, receberá o erro "InvalidoperationException: a coleção foi modificada depois que o enumerador foi instanciado".
Usando Break
Pode ser uma maneira, mas isso pode afetar sua série de operação. O que eu faço nesse caso em simplesmente converter o foreach
ao tradicional for
ciclo
for(i=0; i < List.count; i++)
{
List.Remove();
i--;
}
Isso funciona sem problemas.