2011-08-15 13 views
8

? He estado buscando en todo MSDN y no puedo encontrar un motivo por el que el hilo no pueda ser interrumpido cuando duerma dentro del bloque finally. He intentado abortar sin éxito.¿Por qué el hilo no se interrumpe cuando duerme en el bloque

¿Hay alguna forma de cómo reactivar el hilo cuando se duerme en el bloque finally?

Thread t = new Thread(ProcessSomething) {IsBackground = false}; 
t.Start(); 
Thread.Sleep(500); 
t.Interrupt(); 
t.Join(); 

private static void ProcessSomething() 
{ 
    try { Console.WriteLine("processing"); } 
    finally 
    { 
     try 
     { 
      Thread.Sleep(Timeout.Infinite); 
     } 
     catch (ThreadInterruptedException ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
    } 
} 

Sorprendentemente MSDN afirma hilo se puede abortar en bloque finally: http://msdn.microsoft.com/en-us/library/aa332364(v=vs.71).aspx "Hay una posibilidad de que el hilo podría abortar mientras que un bloque finally se está ejecutando, en cuyo caso se aborta el bloque finally"

edición encuentro comentario Hans Passant como mejor respuesta ya que esto explica por qué hilo a veces puede y no puede ser interrumpida/abortado en el bloque finally. Y es entonces cuando el proceso se está apagando. gracias

+3

No sé la respuesta a la pregunta, pero I * no * sé que, como regla general, evito 'interrupción () 'como un mecanismo para la señalización entre hilos. Hay muchas más API predecibles: 'Monitor',' {Manual | Auto} ResetEvent', etc. –

+0

Estaba buscando más explicaciones/documentación cuando Interrupt/Abort no funciona finalmente y por qué MSDN dice lo contrario. Mi ejemplo es un tipo de sondeo más complejo y ahora lo estoy leyendo en waithandles como se sugiere. – Marek

+3

Hay dos tipos de abortos de hilos. El amigable no interrumpirá el código en un bloque final. El antipático se invoca cuando el proceso se cierra en un subproceso que tiene IsBackground igual a verdadero o cuando termina en una excepción no controlada. Ahora no importa que el código haya sido interrumpido bruscamente. –

Respuesta

8

Deben evitarse los hilos de aborto e interrupción si es posible, ya que esto puede dañar el estado de un programa en ejecución. Por ejemplo, imagine que abortó un hilo que mantenía bloqueos abiertos a los recursos, estos bloqueos nunca se liberarían.

En lugar considerar el uso de un mecanismo de señalización de modo que las roscas pueden cooperar entre sí y así manejar el bloqueo y desbloqueo de gracia, por ejemplo:

private readonly AutoResetEvent ProcessEvent = new AutoResetEvent(false); 
    private readonly AutoResetEvent WakeEvent = new AutoResetEvent(false); 

    public void Do() 
    { 
     Thread th1 = new Thread(ProcessSomething); 
     th1.IsBackground = false; 
     th1.Start(); 

     ProcessEvent.WaitOne(); 

     Console.WriteLine("Processing started..."); 

     Thread th2 = new Thread(() => WakeEvent.Set()); 
     th2.Start(); 

     th1.Join(); 
     Console.WriteLine("Joined"); 
    } 

    private void ProcessSomething() 
    { 
     try 
     { 
      Console.WriteLine("Processing..."); 
      ProcessEvent.Set(); 
     } 
     finally 
     { 
      WakeEvent.WaitOne(); 
      Console.WriteLine("Woken up..."); 
     } 
    } 

actualización

Todo un bajo nivel interesante problema. Aunque Abort() está documentado, Interrupt() es mucho menos.

La respuesta breve a su pregunta es no, no puede reactivar un hilo en un bloque finally llamando al Abort o Interrupt en él.

No poder abortar o interrumpir los hilos en los bloques finally es por diseño, simplemente para que finalmente los bloques tengan la oportunidad de ejecutarse como era de esperar. Si pudieras abortar e interrumpir los hilos en bloques finalmente, esto podría tener consecuencias no deseadas para las rutinas de limpieza, y así dejar la aplicación en un estado dañado, no es bueno.

Un ligero matiz con la interrupción del hilo, es que se puede haber emitido una interrupción contra el hilo en cualquier momento antes de entrar en el bloque finally, pero mientras no estaba en un estado SleepWaitJoin (es decir, no bloqueado). En este caso, si había una llamada de bloqueo en el bloque finally, lanzaría inmediatamente un ThreadInterruptedException y se bloquearía el bloque finally. Finalmente la protección del bloque lo impide.

Además de protección en bloques finally, esto se extiende para probar bloques y también CERs ( Constrained Execution Region) que se pueden configurar en código de usuario para evitar que se genere un rango de excepciones hasta después de la ejecución de una región, muy útil para bloques críticos del código que debe se completa y demora el aborto.

La excepción (sin juego de palabras) a esto es lo que se llama Rude Aborts. Estos son ThreadAbortExceptions planteados por el entorno de alojamiento CLR en sí. Estos pueden provocar que se salgan los bloques finally y catch, pero no CERs. Por ejemplo, el CLR puede generar Abortos groseros en respuesta a los hilos que ha tardado demasiado en realizar su trabajo \ exit, p. cuando intenta descargar un AppDomain o ejecutar código dentro de SQL Server CLR. En su ejemplo particular, cuando su aplicación se apaga y el AppDomain se descarga, el CLR emitiría un aborto brusco en el hilo de dormir ya que habría un tiempo de espera de descarga de AppDomain.

Abortar e interrumpir en bloques finalmente no va a suceder en el código de usuario, pero hay un comportamiento ligeramente diferente entre los dos casos.

Abortar

Al llamar Abort en un hilo en un bloque finally, el subproceso de llamada se bloquea. Esta es documented:

El subproceso que llama Abortar podría bloquear si el hilo que está siendo abortado se encuentra en una zona protegida de código, como un bloque catch, finally, o región ejecución limitada.

En el caso de abortar si el sueño no era infinita:

  1. El subproceso de llamada emitirá una Abort pero bloquear aquí hasta que se sale del bloque finally es decir que se detenga aquí no se detiene inmediatamente a la declaración Join.
  2. El hilo de llamada tiene su estado establecido en AbortRequested.
  3. El destinatario continúa durmiendo.
  4. Cuando el destinatario se despierta, ya que tiene un estado de AbortRequested, continuará ejecutando el código de bloque finalmente y luego se "evaporará", es decir, saldrá.
  5. Cuando la hebra abortada abandona el bloque finally: no se produce ninguna excepción, no se ejecuta ningún código después de la ejecución del bloque finally, y el estado del subproceso es Aborted.
  6. El subproceso de llamada está desbloqueado, continúa con la instrucción Join y pasa inmediatamente cuando el subproceso llamado ha salido.

Así que dado el ejemplo con un sueño infinito, el subproceso de llamada se bloqueará para siempre en el paso 1.

interrupción

En el caso de interrupción si el sueño no era infinita:

No tan bien documentado ...

  1. El subproceso de llamada emitirá una Interrupt y continuar de ejecución.
  2. El hilo de llamada se bloqueará en la instrucción Join.
  3. El hilo de llamada entrante tiene su estado configurado para generar una excepción en la siguiente llamada de bloqueo, pero lo más importante es que está desbloqueado, es decir, como desbloqueado, es decir, despertado.
  4. El destinatario continúa durmiendo.
  5. Cuando el destinatario se despierta, continuará ejecutando el bloque finally.
  6. Cuando el hilo interrumpido abandona el bloque finally lanzará un ThreadInterruptedException en su siguiente llamada de bloqueo (ver ejemplo de código a continuación).
  7. El subproceso de la llamada "une" y continúa a medida que el hilo se llama ha salido, sin embargo, el no controlada ThreadInterruptedException en el paso 6 se ha aplanado ahora el proceso ...

Así que de nuevo da el ejemplo con un sueño infinito, el subproceso de llamada se bloqueará para siempre, pero en el paso 2.

Resumen

Así que, aunque Abort y Interrupt tener un comportamiento ligeramente diferente, que van tanto resultado en el hilo llamado dormir para siempre, y el subproceso de llamada bloqueo para siempre (en tu ejemplo).

Sólo un grosero Abortar puede forzar un hilo bloqueado para salir de un bloque finally, y éstos sólo pueden ser planteada por la propia CLR (ni siquiera se puede utilizar la reflexión para diddle ThreadAbortException.ExceptionState, ya que hace una llamada CLR interna para obtener el AbortReason - no hay oportunidad de ser fácilmente malvado allí ...).

El CLR evita que el código de usuario haga que los bloques finalmente se eliminen prematuramente por nuestro propio bien, ayuda a prevenir el estado dañado.

Para un ejemplo del comportamiento ligeramente diferente con Interrupt:

internal class ThreadInterruptFinally 
{ 
    public static void Do() 
    { 
     Thread t = new Thread(ProcessSomething) { IsBackground = false }; 
     t.Start(); 
     Thread.Sleep(500); 
     t.Interrupt(); 
     t.Join(); 
    } 

    private static void ProcessSomething() 
    { 
     try 
     { 
      Console.WriteLine("processing"); 
     } 
     finally 
     { 
      Thread.Sleep(2 * 1000); 
     } 

     Console.WriteLine("Exited finally..."); 

     Thread.Sleep(0); //<-- ThreadInterruptedException 
    } 
} 
+0

+1 Gran respuesta. –

4

El objetivo de un bloque finally es mantener algo que no se verá afectado por una interrupción o aborto y que se ejecutará hasta la finalización normal pase lo que pase. Permitir que un bloque finally se cancele o se interrumpa prácticamente anulará el punto. Lamentablemente, como ha notado, los bloques finally pueden ser abortados o interrumpidos debido a varias condiciones de carrera. Por esta razón, verá a muchas personas aconsejándoles que no interrumpan o aborten los hilos.

En su lugar, use diseño cooperativo. Si se supone que se debe interrumpir un hilo, en lugar de llamar al Sleep, utilice una espera temporizada. En lugar de llamar al Interrupt, señale lo que espera el hilo.

Cuestiones relacionadas