2012-04-04 12 views
7

estoy bucle a través de una lista de elementos con foreach, así:lista de Modificación de otro hilo, mientras la iteración (C#)

foreach (Type name in aList) { 
    name.doSomething(); 
} 

Sin embargo, en un otro hilo que estoy llamando algo así como

aList.Remove(Element); 

Durante el tiempo de ejecución, esto causa una InvalidOperationException: se modificó la colección; la operación de enumeración no se puede ejecutar. ¿Cuál es la mejor manera de manejar esto (yo prefiero que sea bastante simple, incluso a costa del rendimiento)?

Gracias!

Respuesta

7

Tema A:

lock (aList) { 
    foreach (Type name in aList) { 
    name.doSomething(); 
    } 
} 

Tema B:

lock (aList) { 
    aList.Remove(Element); 
} 

Esto por supuesto es muy malo para el rendimiento.

+0

Gracias, funciona como un encanto :) Esperemos que no voy a tener ningún problema de rendimiento mediante el uso de eso .. –

9

¿Cuál es la mejor manera de manejar esto (yo preferiría que sea bastante simple, incluso a costa del rendimiento)?

Fundamentalmente: no intente modificar una colección no segura para subprocesos de varios subprocesos sin bloquear. El hecho de que estás iterando es casi irrelevante aquí, simplemente te ayudó a encontrarlo más rápido. Hubiera sido inseguro que dos hilos estuvieran llamando al Remove al mismo tiempo.

O utilizar una colección segura para subprocesos como ConcurrentBag o asegurarse de que sólo un hilo hace nada con la colección a la vez.

+0

no pensaba en eso, pero ahora que usted ha dicho que tiene sentido. Gracias, probablemente haga uso de eso más tarde, pero para este caso, el bloqueo funciona bien. –

0

si lo que desea es evitar la excepción de uso

foreach (Type name in aList.ToArray()) 
{ name.doSomething(); } 

tenga en cuenta la doSomething() se ejecuta también en el caso de que el elemento fue eliminado en el otro hilo

+1

Desafortunadamente 'ToArray' y' Remove' podrían correr posiblemente dando como resultado un tipo diferente de excepción. ¡Es una buena idea! Consulte mi [respuesta] (http://stackoverflow.com/a/10018399/158779) para ver cómo funciona esto correctamente. –

+0

Oh ... y bienvenidos a stackoverflow :) –

0

no puedo específicamente diga de su pregunta, pero (parece) está realizando una acción en cada elemento y luego quitándolo. Es posible que desee consultar el BlockingCollection<T>, que tiene un método que llama al GetConsumingEnumerable() para ver si es adecuado para usted. Aquí hay una pequeña muestra.

void SomeMethod() 
{ 
    BlockingCollection<int> col = new BlockingCollection<int>(); 

    Task.StartNew(() => { 

     for (int j = 0; j < 50; j++) 
     { 
      col.Add(j); 
     } 

     col.CompleteAdding(); 

    }); 

    foreach (var item in col.GetConsumingEnumerable()) 
    { 
     //item is removed from the collection here, do something 
     Console.WriteLine(item); 
    } 
} 
9

# 1:

El método más simple y menos eficiente es la creación de una sección crítica para los lectores y escritores.

// Writer 
lock (aList) 
{ 
    aList.Remove(item); 
} 

// Reader 
lock (aList) 
{ 
    foreach (T name in aList) 
    { 
    name.doSomething(); 
    } 
} 

Método # 2:

Esto es similar al método # 1, pero en lugar de mantener el bloqueo durante toda la duración del bucle foreach que sería copiar la colección primero y luego iterar sobre la dupdo. Método

// Writer 
lock (aList) 
{ 
    aList.Remove(item); 
} 

// Reader 
List<T> copy; 
lock (aList) 
{ 
    copy = new List<T>(aList); 
} 
foreach (T name in copy) 
{ 
    name.doSomething(); 
} 

# 3:

Todo depende de su situación específica, pero la forma en que normalmente se utiliza esto es mantener la referencia principal a la colección inmutable. De esta forma, nunca tendrá que sincronizar el acceso en el lado del lector. El lado del escritor de las cosas necesita un lock. El lado del lector no necesita nada, lo que significa que los lectores se mantienen altamente concurrentes. Lo único que debe hacer es marcar la referencia aList como volatile.

// Variable declaration 
object lockref = new object(); 
volatile List<T> aList = new List<T>(); 

// Writer 
lock (lockref) 
{ 
    var copy = new List<T>(aList); 
    copy.Remove(item); 
    aList = copy; 
} 

// Reader 
List<T> local = aList; 
foreach (T name in local) 
{ 
    name.doSomething(); 
} 
+0

Me gusta la variedad de soluciones aquí, pero debe señalarse en los dos primeros ejemplos que 'aList' debe ser un objeto interno de la clase, de lo contrario podría producirse un interbloqueo. –

+0

@DerekW: Sí, es una buena práctica y bien conocida. En realidad, es poco probable que realmente provoque un punto muerto porque los escenarios típicos para tales ocurrencias (bloqueos anidados, etc.) no están en juego aquí. Entonces, a menos que otra parte del código abuse totalmente de 'aList', lo peor que probablemente ocurrirá es una mayor contención de bloqueo. Creo que todo el debate de 'lock (this)' es un poco exagerado y se inclina un poco demasiado hacia el lado de la paranoia de lo que algunos admitirán. Una vez más, sigue siendo una buena práctica evitar dichos modismos. –

+0

Debo señalar también, mientras estoy pensando, que # 3 debe cumplirse * exactamente * como se presentó. Cualquier desviación (como la decisión de omitir la asignación a 'local') probablemente genere fallas aleatorias y espectaculares. Incluso podría rasgar un todo en el espacio-tiempo por lo que sé. –

Cuestiones relacionadas