2009-05-10 47 views
12

Quizás alguien pueda indicarme la dirección correcta, porque estoy completamente perplejo con esto.Extraño "La colección se modificó después de que se haya creado la instancia del enumerador" excepción

que tienen una función que simplemente imprime un LinkedList de las clases:

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("------"); 
    } 

El objeto Component en realidad tiene una costumbre ToString() llamada como tal:

int Id; 
    ... 
    public override String ToString() 
    { 
     return GetType() + ": " + Id; 
    } 

Esta función normalmente funciona bien - sin embargo, Me he encontrado con el problema de que cuando se compila con aproximadamente 30 entradas de la lista, la declaración PrintcomplentListforeach vuelve con un InvalidOperationException: Collection was modified after the enumerator was instantiated.

Ahora como puede ver, no estoy modificando el código dentro del ciclo for, y no he creado ningún subproceso explícitamente, aunque esto se encuentra dentro de un entorno XNA (si es que importa). Cabe señalar que la impresión es lo suficientemente frecuente como para que la salida de la consola reduzca la velocidad del programa en su conjunto.

Estoy completamente perplejo, ¿alguien más ha topado con esto?

+0

Eso suena extremadamente extraño. ¿Podría publicar un programa corto pero * completo * para que podamos intentar reproducirlo? –

+0

Veré lo que puedo hacer. – cyberconte

+0

No puedo obtener un pequeño programa para replicar el comportamiento, así que voy a ver cómo obtener una implementación segura de subprocesos de LinkedList para ver si detecta algo. – cyberconte

Respuesta

11

Sospecho que el lugar para comenzar a buscar estará en cualquier lugar donde manipule la lista, es decir, insertar/quitar/reasignar elementos. Mi sospecha es que habrá un callback/even-handler en algún lugar que sea despedido de forma asincrónica (tal vez como parte del XNA paint etc loops), y que esté editando la lista, lo que básicamente causa este problema como una condición de carrera.

Para comprobar si este es el caso, ponga alguna salida de depuración/rastreo alrededor de los lugares que manipulan la lista, y vea si alguna vez (y en particular, justo antes de la excepción) ejecuta el código de manipulación al mismo tiempo su salida de la consola:

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

Por desgracia, este tipo de cosas son a menudo un dolor de depurar, como cambiar el código para investigar a menudo cambia el problema (una Heisenbug).

Una respuesta sería sincronizar el acceso; es decir, en todos los lugares que editar la lista, utilice un lock en torno a la operación 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 
} 

y en su devolución de llamada (o lo que sea)

private void SomeCallback() 
{ 
    lock(syncLock) 
    { 
     components.AddLast(foo); 
    } 
} 

En particular, una "operación completa" podría incluir:

  • verificación de la cuenta y foreach/for
  • cheque por existencia y insertar/eliminar
  • etc

(es decir,no las operaciones individuales/discretas, sino unidades de trabajo)

+0

tenga en cuenta también que registra el id. De subproceso cuando escribe sus líneas de rastreo pueden ayudarlo a identificar si hay hilos en competencia y, por lo tanto, la posibilidad de una carrera. –

4

En lugar de foreach, uso while(collection.count >0) y uso collection[i].

2

No sé si esto es relevante para el OP, pero tuve el mismo error y encontré este hilo durante una búsqueda en Google. Pude resolverlo agregando un descanso después de eliminar un elemento en el ciclo.

foreach(Weapon activeWeapon in activeWeapons){ 

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

Si se omite el descanso, obtendrá el error "InvalidOperationException: Colección se modificó después de que el empadronador se crea una instancia."

+0

Sin embargo, esto introduce otro error: ¡ninguna de sus armas en la lista después del primer arma fuera de rango no tendrá sus posiciones modificadas! He tratado esto en el pasado creando una segunda Lista de cosas para eliminar de la primera lista, llenándola de un foreach, luego haciendo un foreach sobre la segunda lista y eliminando cada elemento de la primera lista. – twon33

0

Usar el descanso podría ser una forma pero puede afectar su serie de operación. Lo que hago en ese caso simplemente en convertir el foreach para tradicional para bucle

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

Esto funciona sin ningún problema.

+0

Eso podría funcionar, pero en este caso, la iteración es sobre los objetos de una colección. foreach (var obj en collection.keys) ... – Futureproof

Cuestiones relacionadas