lock español c# multithreading queue

lock c# español



En C#, ¿sería mejor usar Queue.Synchronized o lock() para seguridad de subprocesos? (5)

Tengo un objeto Queue que necesito asegurar para que sea seguro para subprocesos. ¿Sería mejor usar un objeto de bloqueo como este?

lock(myLockObject) { //do stuff with the queue }

O se recomienda utilizar Queue.Sinnchronized como este:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

Al leer los documentos de MSDN dice que debo usar Queue.Synchronized para que sea seguro para subprocesos, pero luego da un ejemplo usando un objeto de bloqueo. Del artículo de MSDN:

Para garantizar la seguridad del subproceso de la cola, todas las operaciones se deben realizar solo a través de este reiniciador.

Enumerar a través de una colección no es intrínsecamente un procedimiento seguro para subprocesos. Incluso cuando una colección está sincronizada, otros subprocesos aún pueden modificar la colección, lo que hace que el enumerador genere una excepción. Para garantizar la seguridad del subproceso durante la enumeración, puede bloquear la recopilación durante toda la enumeración o atrapar las excepciones resultantes de los cambios realizados por otros subprocesos.

Si la llamada Sincronizada () no garantiza la seguridad de la hebra, ¿de qué sirve? ¿Me estoy perdiendo de algo?


Con frecuencia hay una tensión entre las demandas de ''colecciones seguras para hilos'' y el requisito de realizar múltiples operaciones en la colección de forma atómica.

Así, Synchronized () le ofrece una colección que no se destruirá si varios subprocesos le agregan elementos simultáneamente, pero mágicamente no le proporciona una colección que sabe que durante una enumeración, nadie más debe tocarla.

Además de la enumeración, las operaciones comunes como "¿este elemento ya está en la cola? No, entonces lo agregaré" también requieren sincronización, que es más amplia que solo la cola.


De esta forma, no es necesario bloquear la cola solo para descubrir que estaba vacía.

object item; if (queue.Count > 0) { lock (queue) { if (queue.Count > 0) { item = queue.Dequeue(); } } }


Hay un problema importante con los métodos Synchronized en la biblioteca de colecciones anterior, ya que se sincronizan a un nivel de granularidad demasiado bajo (por método en lugar de por unidad de trabajo).

Hay una condición de carrera clásica con una cola sincronizada, que se muestra a continuación donde se verifica el Count para ver si es seguro para quitar la cola, pero luego el método Dequeue arroja una excepción que indica que la cola está vacía. Esto ocurre porque cada operación individual es segura para subprocesos, pero el valor de Count puede cambiar entre cuando lo consulta y cuando usa el valor.

object item; if (queue.Count > 0) { // at this point another thread dequeues the last item, and then // the next line will throw an InvalidOperationException... item = queue.Dequeue(); }

Puede escribir esto con seguridad usando un bloqueo manual alrededor de toda la unidad de trabajo (es decir, verificando el conteo y dequeuando el artículo) de la siguiente manera:

object item; lock (queue) { if (queue.Count > 0) { item = queue.Dequeue(); } }

Por lo tanto, como no puede extraer de forma segura nada de una cola sincronizada, no me molestaría con eso y solo usaría el bloqueo manual.

.NET 4.0 debería tener un conjunto completo de colecciones seguras para hilos implementadas correctamente, pero eso todavía está a un año de desafortunadamente.


Me parece claro que usar un bloqueo (...) {...} es la respuesta correcta.

Para garantizar la seguridad del subproceso de la cola, todas las operaciones se deben realizar solo a través de este reiniciador.

Si otros subprocesos acceden a la cola sin utilizar .Synchronized (), entonces estará en un riachuelo, a menos que todo el acceso a la cola esté bloqueado.


Personalmente, siempre prefiero el bloqueo. Significa que puedes decidir la granularidad. Si solo confía en el contenedor sincronizado, cada operación individual se sincroniza, pero si alguna vez necesita hacer más de una cosa (por ejemplo, iterar sobre toda la colección) debe bloquear de todos modos. En aras de la simplicidad, prefiero tener una sola cosa que recordar: ¡bloquear correctamente!

EDITAR: Como se señala en los comentarios, si puedes usar abstracciones de mayor nivel, eso es genial. Y si usa el bloqueo, tenga cuidado con él: documente lo que espera que esté bloqueado y adquiera / suelte bloqueos durante el menor tiempo posible (más por corrección que por rendimiento). Evite llamar a un código desconocido mientras mantiene un candado, evite los bloqueos anidados, etc.

En .NET 4 hay mucho más soporte para abstracciones de alto nivel (incluido el código de bloqueo). De cualquier manera, todavía no recomendaría el uso de las envolturas sincronizadas.