miércoles, 16 de julio de 2014

Receta C# No. 4-8: Sincronizar la Ejecución de Múltiples Threads usando un Evento

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Diferencias entre EventWaitHandle, AutoResetEvent, y ManualResetEvent
3.1.1 La clase AutoResetEvent
3.1.2 La clase ManualResetEvent
3.1.3 La clase EventWaitHandle
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Nueva receta C# en la que descubriremos otro de los mecanismos de sincronización que dispone .NET Framework. La sincronización basada en eventos también (al igual que con Monitores [cfr. Sincronizar la Ejecución de Múltiples Threads usando un Monitor]) facilita coordinar la ejecución de múltiples threads para permitir el correcto y eficiente acceso y uso de recursos compartidos (e.g., archivos, conexiones de red, memoria de trabajo). Conozcamos este mecanismo con algo de teoría y práctica (sobretodo) usando las clases EventWaitHandle, AutoResetEvent, y ManualResetEvent.

1. Problema

Además de trabajar con monitores (tal como ocurrió en la receta anterior Sincronizar la Ejecución de Múltiples Threads usando un Monitor), nos han informado que existen métodos alternativos para la sincronización de la ejecución de múltiples threads que acceden a recursos compartido (e.g., memoria de trabajo, conexiones de red, archivos); luego, debemos buscar esta alternativa para aumentar nuestro entendimiento y utillaje para atacar problemas de acceso y ejecución sincrónicos.

2. Solución

Además de contar con la clase Monitor [6] para la sincronización de threads, el Framework .NET también posee varios artefactos que facilitan esta tarea con el uso de:
  • EventWaitHandle
  • AutoResetEvent, y 
  • ManualResetEvent
La cual está basada en eventos.

3. Discusión de la Solución


Las clases EventWaitHandle, AutoResetEvent, y ManualResetEvent se hayan en el nombre de espacios System.Threading. La jerarquía de herencia es la que se muestra en la Figura 1.
Jerarquía de herencia WaitHandle
Figura 1. Jerarquía de herencia WaitHandle.
Estas tres clases se caracterizan por proveer el mecanismo de sincronización basado en señales, es decir, a través de la emisión de notificaciones (por medio de métodos) a los threads en ejecución. Las señalas o notificaciones corresponden a dos únicos valores [6]:
  • signaled, y 
  • unsignaled

Para lograr este modelo de sincronización, la clase WaitHandle [7] provee una serie de métodos (ver Figura 2 [1]) para conocer el estado de los objetos de eventos (de los que hablaremos en breve más adelante).
Métodos de la clase WaitHandle
Figura 2. Métodos de la clase WaitHandle [1].

3.1 Diferencias entre EventWaitHandle, AutoResetEvent, y ManualResetEvent

3.1.1 Clase AutoResetEvent

La clase AutoResetEvent [8] notifica a los threads en espera a ser atentidos (uno por uno) o acceder a una región de código (región crítica) compartido que un evento ha ocurrido y puede continuar con la ejecución del proceso. Como explican en [6] este tipo de gestión de la sincronización se analoga al funcionamiento de un torniquete (ver imágenes). Así:
  1. Permitir el paso de una sola persona en un momento dado.
  2. Las personas que están detrás, deben esperar a que el torniquete esté libre para pasar: WaitOne.
  3. Y, se debe pasar o insertar el tiquete para continuar: Set.

En la Figura 3 [6] queda mejor descrito este proceso.
Funcionamiento de la clase AutoResetEvent
Figura 3. Funcionamiento de la clase AutoResetEvent [12].

En esta descripción pictórica, los threads Thread1, Thread2, y Thread3 invocan al método WaitOne, tendrán que esperar a que el thread Main o cualquier otro thread invoquen al método Set. Cada vez que se invoque este método, uno de los threads mencionados pasará por el torniquete.


Para demostrar uso, creemos un ejemplo que esté compuesto por una instancia de esta clase y el uso de los métodos WaitOne y Set:


Ejemplo de uso:

Archivo C# UsoAutoResetEvent.cs [enlace alternativo]:

En la línea 11 creamos una instancia de AutoResetEvent a la que le pasamos false a su constructor para indicar que no se invocará ninguna señal al inicio de instanciación. Más adelante, en la línea 15 creamos un nuevo thread y lo iniciamos inmediatamente:

new Thread (ProcesoEspera).Start ();


Al método Main lo ponemos en espera durante un segundo y medio (1500ms). Mientras que por otro lado, el método ProcesoEspera se haya en ejecución, en la línea 24 se muestra un mensaje en la salida estándar sobre el estado actual de ejecución del thread, o más precisamente, el punto que ha alcanzado su ejecución precisamente al invocar al método WaitOne:


waitHandle.Set();


Cuando se genera la señal, línea 19, en el método Main, el hilo de ejecución asociado al método ProcesoEspera continua su ejecución y muestra el mensaje de la línea 26.



En la Figura 3 [3] se describe pictóricamente todo el flujo de ejecución para este ejemplo.


Flujo de ejecución assembly UsoAutoResetEvent.exe
Figura 4. Flujo de ejecución assembly UsoAutoResetEvent.exe.
Compilación:


  1. csc /target:exe UsoAutoResetEvent.cs

Ejecución assembly:


  1. .\UsoAutoResetEvent.exe

> Prueba de ejecución (ideone.com).

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

3.1.2 Clase ManualResetEvent


La clase ManualResetEvent [4] funciona de manera distinta, no como un torniquete sino como una puerta. Es decir, cuando varios threads (e.g.Thread1Thread2, y Thread3) desde su proceso asociado invoquen al método WaitOne quedarán frente a la puerta esperando a que se abra. La apertura se realiza cuando un thread, ya sea Main o cualquier otro, invoque al método Set. Con esta llamada, todos los threads continuarán con la invocación. En la Figura 6 queda mejor descrito este proceso.
Funcionamiento clase ManualResetEvent
Figura 6. Funcionamiento de la clase ManualResetEvent [12].
(Gracias a Sreekanth por su explicación en su post [12].)

Ejemplo de uso:

En la línea 10 creamos una instancia de ManualResetEvent a la que le pasamos false para indicar que no se debe generar ninguna señal (o notificación) en el momento de su instanciación. En el método Main creamos 3 threads dentro del ciclo for. Cada uno de estos threads están asociados con el método Proceso. Dentro de este proceso (método), exactamente en la línea 40 se invoca al método WaitOne. Cuando cada thread alcanza este punto, queda en espera. No es sino hasta cuando se invoca al método Set en la línea 29 para continuar con el resto de la ejecución del método Proceso.

Compilación:


  1. csc /target:exe UsoManualResetEvent.cs

Ejecución assembly:


  1. .\UsoManualResetEvent.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoManualResetEvent.exe
Figura 7. Ejecución assembly UsoManualResetEvent.exe.

3.1.3 Clase EventWaitHandle

La clase EventWaitHandle [13] puede operar de las dos maneras mencionadas anteriormente en las clases AutoResetEvent y ManualResetEvent. Una de las versiones de su contructor permite especificar el estado de la notificación (false o true), y el modo de funcionamiento: automático, manual. Estos dos modos se obtienen de la enumeración EventResetMode [14] (System.Threading).

Ejemplo de uso:

Archivo C# UsoEventWaitHandle.cs [enlace alternativo]:

A diferencia del ejemplo en el código C# UsoAutoResetEvent.cs (sección 3.1.1), en la línea 11 creamos una instancia de EventWaitHandle de la siguiente manera:

EventWaitHandle waitHandle = new EventWaitHandle (false, EventResetMode.AutoReset);


Los argumentos del constructor corresponden con la no generación de señal o notificación en el instante de instanciación, y su modo de funcionamiento automático con el valor de AutoReset de la enumeración EventResetMode.


Compilación:


  1. csc /target:exe UsoEventWaitHandle.cs

Ejecución assembly:


  1. .\UsoEventWaitHandle.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoEventWaitHandle.exe
Figura 7. Ejecución assembly UsoEventWaitHandle.exe.

4. Práctica: Código C#

Este ejemplo es una adaptación del hallado en [1], y consiste en demostrar varias de las funcionalidades de la clase EventWaitHandle en modo manual. Durante la ejecución, el usuario de la aplicación genera las señalas de estado (unsignaled, y signaled) de los threads a través de la pulsación de la tecla Enter. En la salida estándar se muestra mensajes de generación de eventos de los threads cada 2 segundos. Veamos esto en código C# para conocer su funcionamiento:

Archivo C# RecetaEventWaitHandle.cs [enlace alternativo]:


[Nota: Omito la descripción del código; este cuenta con comentarios.

Compilación:


  1. csc /target:exe RecetaEventWaitHandle.cs

Ejecución assembly:


  1. .\RecetaEventWaitHandle.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly RecetaEventWaitHandle.exe
Figura 8. Ejecución assembly RecetaEventWaitHandle.exe.

5. Conclusiones

Hemos conocido un nuevo método de sincronización de threads ejecución. Esta sincronización se logra a través de varios artefactos que hemos descritos con varios detalles de funcionamiento en la sección 3. La sincronización está basada en eventos que son generados por llamadas a métodos (e.g., Set()) de las clases EventWaitHandle, AutoResetEvent, y ManualResetEvent. Para la próxima receta estudiaremos otro de los métodos de sincronización conocido como Mutex.

6. Glosario

  • Delegado
  • Evento
  • Hilo
  • Proceso
  • Sincronización
  • Thread

7. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: EventWaitHandle Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle%28v=vs.110%29.aspx
[3]: Threading in C# - Part 2 - Basic Synchronization - http://www.albahari.com/threading/part2.aspx
[4]: ManualResetEvent Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent%28v=vs.110%29.aspx
[5]: Receta C# No. 4-7: Sincronizar la Ejecución de Múltiples Threads usando un Monitor | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/07/receta-csharp-no-4-7-sincronizar-la-ejecucion-de-multiples-threads-usando-un-monitor.html
[6]: Monitor Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.monitor(v=vs.110).aspx
[7]: WaitHandle Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.waithandle%28v=vs.110%29.aspx
[8]: AutoResetEvent Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent(v=vs.110).aspx
[9]: Turnstile - Bing Images - http://www.bing.com/images/search?q=Turnstile&go=Submit&qs=n&form=QBIR&pq=turnstile&sc=0-0&sp=-1&sk=
[10]: WaitHandle.WaitOne Method (System.Threading) - http://msdn.microsoft.com/en-us/library/58195swd(v=vs.110).aspx
[11]: EventWaitHandle.Set Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle.set(v=vs.110).aspx
[12]: Multithreading in .Net and C#: EventWaitHandler: AutoResetEvent and ManualResetEvent - http://multithreads.blogspot.com/2007/09/eventwaithandler-autoresetevent-and.html
[13]: EventWaitHandle Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle(v=vs.110).aspx
[14]: EventResetMode Enumeration (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.eventresetmode(v=vs.110).aspx


M

1 comentario:

  1. Excelente explicacion, muy buenos ejemplos, me sirvio bastante para volver sincrono la lectura del puerto COM!!!

    ResponderEliminar

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