¿Un objeto bloqueado permanece bloqueado si ocurre una excepción dentro de él?

85

En la aplicación de subprocesos ac #, si tuviera que bloquear un objeto, digamos una cola, y si ocurre una excepción, ¿el objeto permanecerá bloqueado? Aquí está el pseudocódigo:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

Según tengo entendido, el código después de la captura no se ejecuta, pero me he estado preguntando si se liberará el bloqueo.

Khadaji
fuente
1
Como pensamiento final (ver actualizaciones), probablemente solo debería mantener el bloqueo durante el tiempo de desconexión ... realice el procesamiento fuera del bloqueo.
Marc Gravell
1
El código después de la captura se ejecuta porque se maneja la excepción
cjk
Gracias, debo haberme perdido esa, ¿debo eliminar esta pregunta?
Vort3x
4
Parece que el código de muestra no es bueno para esta pregunta, pero la pregunta es bastante válida.
SalvadorGomez
Por C # Designer - Lock & Exception
Jivan

Respuestas:

88

Primero; ¿ha considerado TryParse?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

El bloqueo se liberará por 2 razones; primero, lockes esencialmente:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

Segundo; detecta y no vuelve a lanzar la excepción interna, por lo que locknunca ve una excepción. Por supuesto, mantendrá el bloqueo durante la duración de un MessageBox, lo que podría ser un problema.

Por lo tanto, se publicará en todas las excepciones, excepto en las catastróficas irrecuperables más fatales.

Marc Gravell
fuente
14
Soy consciente del tryparse, pero no es realmente relevante para mi pregunta. Este era un código simple para explicar la pregunta, no una verdadera preocupación con respecto al análisis. Reemplace el análisis con cualquier código que fuerce la captura y lo haga sentir cómodo.
Khadaji
13
¿Qué tal lanzar una nueva excepción ("con fines ilustrativos"); ;-p
Marc Gravell
1
Excepto si se TheadAbortExceptionproduce entre Monitor.Entery try: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Igal Tabachnik
2
"Fatales y catastróficas excepciones irrecuperables" como cruzar los arroyos.
Phil Cooper
98

Observo que nadie ha mencionado en sus respuestas a esta vieja pregunta que liberar un candado en una excepción es algo increíblemente peligroso. Sí, las sentencias de bloqueo en C # tienen semántica "finalmente"; cuando el control sale de la cerradura de forma normal o anormal, la cerradura se libera. Todos están hablando de esto como si fuera algo bueno, ¡pero es algo malo! Lo correcto si tiene una región bloqueada que arroja una excepción no controlada es terminar el proceso enfermo inmediatamente antes de que destruya más datos de usuario , no liberar el bloqueo y continuar .

Mírelo de esta manera: suponga que tiene un baño con una cerradura en la puerta y una fila de gente esperando afuera. Una bomba estalla en el baño, matando a la persona que está allí. Su pregunta es "en esa situación, ¿se desbloqueará automáticamente la cerradura para que la siguiente persona pueda entrar al baño?" Sí, lo hará. Eso no es algo bueno. ¡Una bomba estalló allí y mató a alguien! Es probable que la tubería esté destruida, la casa ya no sea estructuralmente sólida y podría haber otra bomba allí . Lo correcto es sacar a todos lo más rápido posible y demoler toda la casa.

Quiero decir, piénselo bien: si bloqueó una región de código para leer desde una estructura de datos sin que se mute en otro hilo, y algo en esa estructura de datos arrojó una excepción, es muy probable que sea porque la estructura de datos está corrupto . Los datos del usuario ahora están desordenados; no desea intentar guardar datos de usuario en este momento porque luego está guardando datos corruptos . Simplemente finalice el proceso.

Si bloqueó una región de código para realizar una mutación sin que otro hilo leyera el estado al mismo tiempo, y la mutación se produce, entonces si los datos no estaban corruptos antes, seguro que lo están ahora . Cuál es exactamente el escenario contra el que se supone que protege la cerradura . Ahora, el código que está esperando leer ese estado tendrá acceso inmediato al estado corrupto y probablemente se bloqueará. Nuevamente, lo correcto es terminar el proceso.

No importa cómo lo corte, una excepción dentro de una cerradura es una mala noticia . La pregunta correcta no es "¿se limpiará mi cerradura en caso de una excepción?" La pregunta correcta es "¿cómo me aseguro de que nunca haya una excepción dentro de una cerradura? Y si la hay, ¿cómo puedo estructurar mi programa para que las mutaciones se reviertan a estados buenos anteriores?"

Eric Lippert
fuente
18
Ese problema es bastante ortogonal al bloqueo de la OMI. Si obtiene una excepción esperada, desea limpiar todo, incluidos los bloqueos. Y si recibe una excepción inesperada, tiene un problema, con o sin bloqueos.
CodesInChaos
10
Creo que la situación descrita anteriormente es un generalismo. A veces, las excepciones describen eventos catastróficos. A veces no lo hacen. Cada uno los usa de manera diferente en el código. Es perfectamente válido que una excepción sea una señal de un evento excepcional pero no catastrófico, asumiendo excepciones = catastrófico, el caso de terminación del proceso es demasiado específico. El hecho de que pueda ser un evento catastrófico no quita la validez de la pregunta: el mismo hilo de pensamiento podría llevarlo a nunca manejar ninguna excepción, en cuyo caso se saldrá del proceso ...
Gerasimos R
1
@GerasimosR: De hecho. Dos puntos a destacar. Primero, se debe suponer que las excepciones son catastróficas hasta que se determine que son benignas. En segundo lugar, si se arroja una excepción benigna de una región bloqueada, es probable que la región bloqueada esté mal diseñada; Probablemente esté haciendo demasiado trabajo dentro de la cerradura.
Eric Lippert
1
Con el debido respeto, pero parece que le está diciendo al Sr. Lippert que el diseño en C # del bloque de bloqueo que debajo del capó emplea una declaración final es algo malo. En cambio, deberíamos bloquear por completo todas las aplicaciones que alguna vez tengan una excepción en una declaración de bloqueo que no fue atrapada dentro del bloqueo. Tal vez eso no sea lo que estás diciendo, pero si es así, estoy muy feliz de que el equipo haya tomado la ruta que hizo y no la tuya (¡extremista por razones puristas!). Con todo respeto.
Nicholas Petersen
2
En caso de que no esté claro a qué me refiero con no componible: supongamos que tenemos un método de "transferencia" que toma dos listas, syd, bloquea s, bloquea d, elimina un elemento de s, agrega el elemento ad, desbloquea d, desbloquea s. El método sólo es correcta si nadie intenta transferir de la lista X a la lista Y al mismo tiempo que alguien trata a la transferencia de Y a X . La corrección del método de transferencia no le permite construir una solución correcta para un problema mayor a partir de él, porque las cerraduras son mutaciones inseguras del estado global. Para "transferir" de forma segura, debe conocer todos los bloqueos del programa.
Eric Lippert
43

sí, se lanzará correctamente; lockactúa como try/ finally, con el Monitor.Exit(myLock)en el finalmente, así que no importa cómo salga, se liberará. Como nota al margen, catch(... e) {throw e;}es mejor evitarlo, ya que eso daña el seguimiento de la pila en e; es mejor no atraparlo en absoluto , o alternativamente: usar en throw;lugar de throw e;cuál hace un relanzamiento.

Si realmente quiere saberlo, un bloqueo en C # 4 / .NET 4 es:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 
Marc Gravell
fuente
14

"Se compila una declaración de bloqueo para una llamada a Monitor.Enter, y luego un intento ... bloquear finalmente. En el bloque finalmente, se llama a Monitor.Salir.

La generación de código JIT para x86 y x64 asegura que no se pueda producir un aborto de subproceso entre una llamada Monitor.Enter y un bloque try que le sigue inmediatamente ".

Tomado de: Este sitio

GH.
fuente
1
Hay al menos un caso en el que esto no es cierto: aborto de subprocesos en modo de depuración en versiones .net anteriores a la 4. La razón es que el compilador de C # inserta un NOP entre el Monitor.Entery el try, de modo que la condición "sigue inmediatamente" del Se infringe el JIT.
CodesInChaos
6

Su cerradura se liberará correctamente. A lockactúa así:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

Y los finallybloques están garantizados para ejecutarse, sin importar cómo salga del trybloque.

Ry-
fuente
En realidad, no se "garantiza" la ejecución de ningún código (podría tirar del cable de alimentación, por ejemplo), y no es exactamente lo que parece un candado en 4.0 - ver aquí
Marc Gravell
1
@MarcGravell: Pensé en poner dos notas al pie sobre esos dos puntos exactos. Y luego pensé que no importaría mucho :)
Ry-
1
@MarcGravel: Creo que todos asumen que siempre uno no está hablando de una situación de 'desconectar', ya que no es algo sobre lo que un programador tenga control :)
Vort3x
5

Solo para agregar un poco a la excelente respuesta de Marc.

Situaciones como esta son la verdadera razón de la existencia de la lockpalabra clave. Ayuda a los desarrolladores a asegurarse de que el bloqueo se libere en el finallybloque.

Si se ve obligado a usar Monitor.Enter/ Exiteg para admitir un tiempo de espera, debe asegurarse de colocar la llamada a Monitor.Exiten el finallybloque para garantizar la liberación adecuada del bloqueo en caso de una excepción.

Brian Rasmussen
fuente