viernes, 27 de junio de 2014

Receta Multithreading en C# No. 1-4: Abortar un Thread en Ejecución

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Versión Abort
3.1.1 Detalles de implementación en .NET Framework
3.1.2 Ejemplos
3.1.2.1 Ejemplo básico
3.1.2.2 Detener un thread en un ciclo infinito
3.1.2.3 Verificación de estados de un thread
3.1.2.4 Inmortalidad de un thread
3.2 Veresión Abort(Object)
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Aprovechemos esta nueva oportunidad para hablar acerca de otros de los métodos con los que cuenta la clase System.Threading.Thread: se trata del método que nos sirve para aborta un thread que se encuentre en ejecución, Abort. Exploraremos las versiones sobrecargadas de este método, junto con usos, ejemplos. También introduciré, grosso modo, el funcionamiento de Abort en .NET, y la interacción de este con el sistema operativo. Además, de algunas consideraciones sobre el uso (y peligros) al intentar abortar un método. ¡Empecemos!

1. Problema

Proponer una solución para cancelar un thread que encuentre en ejecución.

2. Solución

La clase Thread (System.Threading) cuenta con el método sobrecargado Abort para abortar la ejecución de un thread. La Figura 1 muestra la lista sobrecargada de este miembro.
Lista sobrecargada del método Thread.Abort
Figura 1. Lista sobrecargada del método Thread.Abort [2].

3. Discusión de la Solución

Discutamos las dos versiones sobrecargadas del método Abort en las siguientes subsecciones.

3.1 Versión Abort()

Interna e implícitamente este método lanza una excepción ThreadAbortException [7] sobre el thread sobre el que se invoco el método Abort para iniciar el proceso de terminación de su ejecución. Esta es su firma [10]:

public void Abort()

Descripción puntual:
  • Parámetros: «Ninguno»
  • Valor de retorno: void

3.1.1 Detalles de implementación

Gracias al poder del Framework .NET, como programadores podemos manipular la operación de abortar un thread en ejecución desde código de aplicación para atrapar la excepción ThreadAbortException y mantener la aplicación en ejecución sin fallas fatales o inconsistencias en tiempo de ejecución.

Por otro lado, vale incluir cómo el Framework .NET lleva a cabo tareas complejas detrás de cámaras para implementar el método Abort: (Extracción literal desde [4].)
  1. Suspend the underlying OS thread to abort.
  2. Set the .NET thread to abort state to include the AbortRequested bit.
  3. Wait for the thread to abort to be interruptible, that is, sleeping, waiting, or joining.
  4. Add an asynchronous procedure call (APC) to thread's APC queue (using the Win32 function QueueUserAPC) and resume the thread.
  5. When the thread to abort moves into an alertable wait state, the scheduler will call the APC, which sets the thread to abort state to AbortInitiated. A thread enters an alertable wait state only when it passes TRUE as bAlertable to a function like SleepEx. If that doesn't happen, the APC queue is never serviced, which means that Thread.Abort is not guaranteed to cause an exception in the thread to abort.
  6. When the Common Language Runtime (CLR) gets back control of the thread to abort, it will return to execution via a "trip" function, which checks all kinds of states for special activity, including if the thread should be aborted.
  7. If the thread's state is set to AbortInitiated, throw the ThreadAbortException, which the thread being aborted can handle via catch and/or finally (or not, as it chooses).
Como es evidente, se trata de un proceso que involucra la interacción con las API (e.g., Win32) del sistema operativo, funciones de bajo nivel, entre otros detalles que son gestionados por el Framework .NET.

3.1.2 Ejemplos

Comprendamos el funcionamiento de este método con estos ejemplos.

3.1.2.1 Uso básico de Abort

El siguiente código de ejemplo demuestra cómo iniciar y detener inmediatamente un thread.

En la línea 10 creamos un nuevo thread y especificamos como método a encapsular en el delegado ThreadStart MostrarMensaje (líneas 30-36). Con la sentencia if (línea 12) validamos que el estado del thread threadNuevo no sea finalizado. Sobre la línea 15 iniciamos la ejecución del thread, e inmediatamente en la línea 18 abortamos su ejecución. Este sentencia evita que no se ejecute ninguna sentencia del cuerpo de implementación del método MostrarMensaje. Solo se ejecutará las sentencias restantes del thread Main.

Compilación:


  1. csc /target:exe UsoBasicoThreadAbort.cs

Ejecución assembly:


  1. .\UsoBasicoThreadAbort.exe

> Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución UsoBasicoThreadAbort.exe
Figura 2. Prueba ejecución UsoBasicoThreadAbort.exe.

3.1.2.2 Detener un thread en ciclo infinito


Este segundo ejemplo demuestra cómo el método Thread.Abort() es capaz de detener la ejecución de un ciclo infinito contenido en un thread.


Archivo C# UsoBasicoThreadAbort2.cs [enlace alternativo]:

El thread creado en la línea 10 declara un delegado compatible con el delegado ThreadStart y se encapsula un método anónimo para declara un ciclo infinito while. El thread nuevoThread se inicia en la línea 21. El thread Main se detiene (pausa) durante 2 segundos (línea 25). El thread nuevoThread se aborta (línea 28).

Compilación:


  1. csc /target:exe UsoBasicoThreadAbort2.cs

Ejecución assembly:


  1. .\UsoBasicoThreadAbort2.exe

Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución assembly UsoBasicoThreadAbort2.exe
Figura 2. Prueba ejecución assembly UsoBasicoThreadAbort2.exe.

3.1.2.3 Verificación estados de un thread


Este ejemplo demuestra los diferentes estados que pueden presentarse antes de ejecutar o abortar un thread.


Archivo C# VerificacionEstadoThread.cs [enlace alternativo]:



La propiedad Thread.ThreadState [8] contiene el estado actual del thread. Esta propiedad la utilizamos para verificar el estado del thread nuevoThread en las líneas 22, 29, 35, y 41. Con este ejemplo, repito, verificamos el estado de un thread antes de iniciarlo (línea 25) y abortarlo (línea 32). Vale mencionar que en la línea 39 se intenta poner en espera al thread Main para dar finalizar la ejecución del thread nuevoThread, pero esté ya se encuentra abortado (independiente de su estado, no se generara ningún problema o error en el flujo de ejecución).


Compilación:


  1. csc /target:exe VerificacionEstadoThread.cs

Ejecución assembly:


  1. .\VerificacionEstadoThread.exe

Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución assembly VerificacionEstadoThread.exe
Figura 3. Prueba ejecución assembly VerificacionEstadoThread.exe.

3.1.2.4 Inmortalidad thread


Este ejemplo demuestra cómo un thread puede recuperarse después de múltiples intentos de abortar su ejecución.


Archivo C# InmortalidadThread.cs [enlace alternativo]:



Creamos un thread nuevo en la línea 10nuevoThread. Este thread tiene encapsulado el método EjecutarTarea. Este método cuenta con un ciclo infinito while (líneas 29-43) integrado por una sentencia try-catch (líneas 31-40) para tratar el lanzamiento de la excepción ThreadAbortException. Sin embargo, en la línea 39 se invoca el método Thread.ResetAbort [9] para cancelar la solicitud del método Abort. Lo anterior provoca que todos los intentos de abortar la ejecución del thread nuevoThread fracasen.


Compilación:


  1. csc /target:exe InmortalidadThread.cs

Ejecución assembly:


  1. .\InmortalidadThread.exe

Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución assembly InmortalidadThread.exe
Figura 4. Prueba ejecución assembly InmortalidadThread.exe.

[Nota: En la Figura 5 muestra que el thread nuevoThread sigue en ejecución después de los intentos de realizar la acción aborto.]

3.2 Versión Abort(Object)

La segunda versión de Abort posee un parámetro que sirve como contenedor de información de estado que puede ser usada para el thread sobre el que se intenta abortar su ejecución. Esta es su firma [11]:

public void Abort(Object stateInfo)

Descripción puntual:
  • Parámetros:
    • Object stateInfo: información de estado para el thread a ser abortado.
Ejemplo de uso:

En la línea 12 creamos un nuevo thread. Lo iniciamos en la línea 13. Esperamos un segundo (línea 15) para permitir que el método EjecutarTarea genere un mensaje en la salida estándar. En la línea 19 abortamos la ejecución del thread threadNuevo, y le pasamos como información la cadena de caracteres Datos desde el thread Main. para indiciar desde donde se invocó el método Abort(Object). Con la línea 22 ponemos en espera al thread Main para que el thread threadNuevo termine su ejecución y pueda mostrar el mensaje que se genera al atrapar la excepción ThreadAbortException en la línea 40.

Compilación:


  1. csc /target:exe UsoAbortObject.cs

Ejecución assembly:


  1. .\UsoAbortObject.exe

Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución assembly UsoAbortObject.exe
Figura 5. Prueba ejecución assembly UsoAbortObject.exe.

4. Práctica: Código C#

Refresquemos los conocimientos conceptuales y empíricos que adquirimos en la sección anterior.

Compilación:


  1. csc /target:exe ImpresorNumeros.cs

Ejecución assembly:


  1. .\ImpresorNumeros.cs

Prueba de ejecución (Ideone):

> Prueba de ejecución (local):
Prueba ejecución assembly ImpresorNumeros.exe
Figura 6. Prueba ejecución assembly ImpresorNumeros.exe.

5. Conclusiones

El uso de las versiones sobrecargadas del método Abort facilitan abortar un thread que se encuentre en ejecución; sin embargo, es importante tener en cuenta que este proceso no se detiene sin generar efectos secundarios, debido a la generación de la excepción ThreadAbortException. A este caso debemos prestarle bastante atención, en particular, cuando intentemos capturar y tratar la excepción generada.En futuras entregas hablaré con más detalle acerca de esta excepción.

6. Glosario

  • Abort
  • Abortar
  • Multithreading
  • OS
  • Sistema Operativo
  • Thread

Literatura & Enlaces

[1]: Multithreading in C# 5.0 Cookbook by Eugene Agafonov. Copyright 2013 Eugene Agafonov, 978-1-84969-764-4.
[2]: Thread.Abort Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.thread.abort(v=vs.110).aspx
[3]: How To Stop a Thread in .NET (and Why Thread.Abort is Evil) - You've Been Haacked - http://haacked.com/archive/2004/11/12/how-to-stop-a-thread.aspx/
[4]: WindowsDevCenter.com - http://www.windowsdevcenter.com/pub/a/dotnet/2003/02/18/threadabort.html
[5]: Abort a Thread in C# - http://www.dotnetheaven.com/article/abort-a-thread-in-csharp
[6]: Threading in C# - Part 4 - Advanced Threading - http://www.albahari.com/threading/part4.aspx
[7]: ThreadAbortException Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.threadabortexception(v=vs.110).aspx
[8]: Thread.ThreadState Property (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.thread.threadstate(v=vs.110).aspx
[9]: Thread.ResetAbort Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.thread.resetabort(v=vs.110).aspx
[10]: Thread.Abort Method (System.Threading) - http://msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.110).aspx
[11]: Thread.Abort Method (Object) (System.Threading) - http://msdn.microsoft.com/en-us/library/5b50fdsz(v=vs.110).aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.