2008-09-15 24 views
10

Necesito enumerar aunque genérico IList <> de objetos. El contenido de la lista puede cambiar, al ser agregado o eliminado por otros subprocesos, y esto matará mi enumeración con una "Colección modificada, la operación de enumeración puede no ejecutarse".Threadsafe para todas las listas de

¿Cuál es una buena manera de hacer threadreafe foreach en un IList <>? Preferiblemente sin clonar la lista completa. No es posible clonar los objetos reales a los que hace referencia la lista.

Respuesta

14

La clonación de la lista es la forma más fácil y mejor, ya que asegura que su lista no cambiará por debajo suyo. Si la lista es demasiado grande para clonar, considere ponerle un candado que debe tomarse antes de leerla/escribir en ella.

+0

¿Qué tal un empadronador con "bloqueo consciente"? El bloqueo de lectura se mantiene hasta que se haya leído el último elemento (movenext devuelve false?) –

1

Forech depende del hecho de que la colección no cambiará. Si desea iterar sobre una colección que puede cambiar, use la normal para construir y prepárese para un comportamiento no determinista. El bloqueo podría ser una mejor idea, dependiendo de lo que esté haciendo.

+0

Sería bueno si .net definen algunas colecciones con bonitas semántica si se cambiaron durante la enumeración (por ejemplo, cada elemento que existe a través de la vida de una enumeración será devuelto exactamente una vez, y los elementos que existen en parte de una enumeración ser devuelto arbitrariamente cero o una vez). Lamentablemente, no conozco ninguno. El comportamiento normal de casi todos los tipos de colección es arrojar una excepción si la colección se edita; una excepción útil es la clase "Colección" estilo VB6; desafortunadamente, eso es bastante limitado en otras formas. – supercat

2

No hay tal operación. Lo mejor que puede hacer es

 

lock(collection){ 
    foreach (object o in collection){ 
     ... 
    } 
} 
0

Ajuste la lista en un objeto de bloqueo para leer y escribir. Incluso puede iterar con varios lectores a la vez si tiene un bloqueo adecuado, que permite múltiples lectores concurrentes, pero también un solo escritor (cuando no hay lectores).

3

Su problema es que una enumeración no permite que IList cambie. Esto significa que debe evitar esto mientras revisa la lista.

algunas posibilidades vienen a la mente:

  • Clonar la lista. Ahora cada enumerador tiene su propia copia para trabajar.
  • Serializar el acceso a la lista. Use un candado para asegurarse de que ningún otro hilo pueda modificarlo mientras se enumera.

O bien, podría escribir su propia implementación de IList e IEnumerator que permita el tipo de acceso paralelo que necesita. Sin embargo, me temo que esto no será simple.

1
ICollection MyCollection; 
// Instantiate and populate the collection 
lock(MyCollection.SyncRoot) { 
    // Some operation on the collection, which is now thread safe. 
} 

De MSDN

3

Usted encontrará que es un tema muy interesante.

El mejor enfoque se basa en el ReadWriteResourceLock que solía tener grandes problemas de rendimiento debido al llamado Problema de Convoy.

El mejor artículo que he encontrado tratando el tema es this one por Jeffrey Richter, que expone su propio método para una solución de alto rendimiento.

1

Por lo tanto, los requisitos son: debe enumerar a través de un IList <> sin hacer una copia al mismo tiempo que agrega y quita elementos.

¿Podría aclarar algunas cosas? ¿Las inserciones y eliminaciones ocurren solo al principio o al final de la lista? Si las modificaciones pueden ocurrir en cualquier punto de la lista, ¿cómo se comportará la enumeración cuando los elementos se eliminen o se agreguen cerca o en la ubicación del elemento actual de la enumeración?

Esto es ciertamente posible mediante la creación de un objeto IEnumerable personalizado con quizás un índice entero, pero solo si puede controlar todo el acceso a su objeto IList <> (para bloquear y mantener el estado de su enumeración). Pero la programación multiproceso es un asunto complicado en las mejores circunstancias, y esta es una probabilidad compleja.

1

el comportamiento por defecto para una sencilla estructura de datos indexada como una lista enlazada, b-árbol, o una tabla hash es enumerar en orden desde el primero hasta el último. No podría causar un problema para insertar un elemento en la estructura de datos después de que el iterador ya había pasado ese punto o insertar uno que el iterador sería enumerar una vez que había llegado, y un evento de este tipo podría ser detectado por la aplicación y manipulación si el la aplicación lo requería. Para detectar un cambio en la colección y lanzar un error durante la enumeración, solo podía imaginar la (mala) idea de alguien de hacer lo que pensaban que el programador querría. De hecho, Microsoft ha arreglado sus colecciones para que funcionen correctamente. Han llamado a sus nuevas colecciones intactas brillantes ConcurrentCollections (System.Collections.Concurrent) en .NET 4.0.

+0

Estás llegando a ser ofensivo aquí. Tal vez podrías editar tu respuesta para que sea un poco menos desagradable. –

+0

Acepto su disgusto por la opinión de Microsoft de que un contrato de iEnumerable debería exigir que se rechace si se modifica la colección. Si tuviera mi druthers, iEnumerable no tendría un método de reinicio, pero habría una interfaz derivada que admitiría el restablecimiento y garantías de que múltiples pases coincidirían o lanzarían, y una segunda interfaz derivada que nunca arrojaría excepciones si se realizaran actualizaciones en el el mismo hilo que el enumerador, pero obedecería una semántica razonable (por ejemplo, cada elemento que exista a lo largo de la enumeración debe devolverse exactamente una vez). – supercat

+1

BTW, también tendría iEnumerable incluir un método de instantánea, algo que no siempre podría proporcionarse utilizando iEnumerable solo (ya que puede ser imposible completar una instantánea sin bloquear algo, e iEnumerable no proporcionaría un medio de haciendo eso). – supercat

Cuestiones relacionadas