Monitor vs bloqueo

88

¿Cuándo es apropiado usar la Monitorclase o la lockpalabra clave para la seguridad de subprocesos en C #?

EDITAR: Por las respuestas hasta ahora, parece que lockes una forma abreviada de una serie de llamadas a la Monitorclase. ¿Para qué sirve exactamente la llamada de bloqueo? O más explícitamente,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Actualizar

Gracias a todos por su ayuda: he publicado otra pregunta como seguimiento de parte de la información que todos proporcionaron. Dado que parece estar bien versado en esta área, he publicado el enlace: ¿Qué tiene de malo esta solución para bloquear y administrar excepciones bloqueadas?

inteligentecaveman
fuente

Respuestas:

89

Eric Lippert habla de esto en su blog: Candados y excepciones no se mezclan

El código equivalente difiere entre C # 4.0 y versiones anteriores.


En C # 4.0 es:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Se basa en Monitor.Entercolocar la bandera de forma atómica cuando se toma el bloqueo.


Y antes fue:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Esto se basa en que no se arroje ninguna excepción entre Monitor.Entery try. Creo que en el código de depuración se violó esta condición porque el compilador insertó un NOP entre ellos y, por lo tanto, hizo posible el aborto del hilo entre ellos.

CódigosInChaos
fuente
Como dije, el primer ejemplo es C # 4 y el otro es el que usan las versiones anteriores.
CodesInChaos
Como nota al margen, C # a través de CLR menciona una advertencia sobre la palabra clave de bloqueo: a menudo, es posible que desee hacer algo para restaurar el estado corrupto (si corresponde) antes de liberar el bloqueo. Dado que la palabra clave lock no nos permite poner cosas en el bloque catch, deberíamos considerar escribir la versión larga try-catch-finalmente para rutinas no triviales.
kizzx2
5
IMO restaurar el estado compartido es ortogonal al bloqueo / subprocesos múltiples. Por lo tanto, debe hacerse con un try-catch / finalmente dentro del lockbloque.
CodesInChaos
2
@ kizzx2: Este patrón sería especialmente bueno con bloqueos de lector-escritor. Si se produce una excepción dentro del código que mantiene un bloqueo de lector, no hay razón para esperar que el recurso protegido se dañe y, por lo tanto, no hay razón para invalidarlo. Si ocurre una excepción dentro de un bloqueo de escritor y el código de manejo de excepciones no indica expresamente que el estado del objeto protegido ha sido reparado, eso sugeriría que el objeto puede estar dañado y debería invalidarse. En mi humilde opinión, las excepciones inesperadas no deberían bloquear un programa, pero deberían invalidar cualquier cosa que pueda estar corrupta.
supercat
2
@ArsenZahray No necesitas Pulseun bloqueo simple. Es importante en algunos escenarios avanzados de subprocesos múltiples. Nunca lo he usado Pulsedirectamente.
CodesInChaos
43

lockes solo un atajo para Monitor.Entercon try+ finallyy Monitor.Exit. Use la instrucción de bloqueo siempre que sea suficiente; si necesita algo como TryEnter, tendrá que usar Monitor.

Lukáš Novotný
fuente
23

Una declaración de bloqueo es equivalente a:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Sin embargo, tenga en cuenta que Monitor también puede esperar () y Pulse () , que a menudo son útiles en situaciones complejas de subprocesos múltiples.

Actualizar

Sin embargo, en C # 4 se implementa de manera diferente:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Gracias a CodeInChaos por sus comentarios y enlaces

Shekhar_Pro
fuente
En C # 4, la declaración de bloqueo se implementa de manera diferente. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
14

Monitores más flexible. Mi caso de uso favorito de usar el monitor es cuando no quiere esperar su turno y simplemente salta :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
fuente
6

Como han dicho otros, lockes "equivalente" a

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Pero solo por curiosidad, lockconservará la primera referencia que le pase y no la tirará si la cambia. Sé que no se recomienda cambiar el objeto bloqueado y no quieres hacerlo.

Pero de nuevo, para la ciencia, esto funciona bien:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... Y esto no:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Error:

Se produjo una excepción del tipo 'System.Threading.SynchronizationLockException' en 70783sTUDIES.exe pero no se manejó en el código de usuario

Información adicional: Se llamó al método de sincronización de objetos desde un bloque de código no sincronizado.

Esto se debe a Monitor.Exit(lockObject);que actuará sobre lo lockObjectque ha cambiado porque stringsson inmutables, entonces lo estás llamando desde un bloque de código no sincronizado ... pero de todos modos. Este es solo un hecho divertido.

André Pena
fuente
"Esto se debe a que Monitor.Salir (lockObject); actuará sobre lockObject". Entonces, ¿bloquear no hace nada con el objeto? ¿Cómo funciona la cerradura?
Yugo Amaryl
@YugoAmaryl, supongo que es porque la declaración de bloqueo tiene en cuenta primero la referencia pasada y luego la usa en lugar de usar la referencia cambiada, como:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.
3

Ambos son lo mismo. lock es una palabra clave aguda y usa la clase Monitor.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx

RobertoBr
fuente
3
Mire msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "De hecho, la palabra clave de bloqueo se implementa con la clase Monitor. Por ejemplo"
RobertoBr
1
la implementación subyacente del bloqueo usa Monitor pero no son lo mismo, considere los métodos proporcionados por el monitor que no existen para el bloqueo, y la forma en que puede bloquear y desbloquear en bloques de código separados.
eran otzap
3

El bloqueo y el comportamiento básico del monitor (entrar + salir) es más o menos el mismo, pero el monitor tiene más opciones que te permite más posibilidades de sincronización.

El candado es un atajo y es la opción para el uso básico.

Si necesita más control, el monitor es la mejor opción. Puede utilizar Wait, TryEnter y Pulse, para usos avanzados (como barreras, semáforos, etc.).

Borja
fuente
1

Bloquear La palabra clave de bloqueo asegura que un hilo esté ejecutando un fragmento de código a la vez.

bloquear (lockObject)

        {
        //   Body
        }

La palabra clave de bloqueo marca un bloque de instrucciones como una sección crítica al obtener el bloqueo de exclusión mutua para un objeto dado, ejecutar una instrucción y luego liberar el bloqueo.

Si otro hilo intenta ingresar un código bloqueado, esperará, se bloqueará, hasta que se libere el objeto.

Monitor El Monitor es una clase estática y pertenece al espacio de nombres System.Threading.

Proporciona un bloqueo exclusivo en el objeto para que solo un hilo pueda entrar en la sección crítica en un momento dado.

Diferencia entre supervisar y bloquear en C #

El candado es el atajo para Monitor. Ingrese con try y finalmente. Las manijas de bloqueo intentan y finalmente se bloquean internamente Bloquear = Supervisar + intentar finalmente.

Si desea tener más control para implementar soluciones avanzadas de subprocesos múltiples utilizando TryEnter() Wait(),Pulse() yPulseAll() métodos, a continuación, la clase Monitor es su opción.

C# Monitor.wait() : un subproceso espera a que otros subprocesos notifiquen.

Monitor.pulse(): Un hilo notifica a otro hilo.

Monitor.pulseAll(): Un hilo notifica a todos los demás hilos dentro de un proceso

Aatrey
fuente
0

Además de todas las explicaciones anteriores, lock es una declaración de C # mientras que Monitor es una clase de .NET ubicada en el espacio de nombres System.Threading.

PureSilence
fuente