domingo, 15 de junio de 2014

Receta No. 4-3 en C#: Cómo Ejecutar un Método Periódicamente

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Delegado TimerCallback
3.2 Clase Timer
4. Práctica: Código Fuente C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Quizás el título de la receta lleve a pensar en una solución basada en un ciclo infinito (while o for, por ejemplo) que invoque determinado método, luego, finalizada su invocación, por medio del método estático Sleep (clase System.Threading.Thread) se espere determinado tiempo para volver a lanzar de nuevo el método. Quizás esta solución resulte "atractiva" para un determinado escenario… Empero, quiero, a través de esta receta presentar una solución más sofisticada usando threads de ejecución por separado para ejecutar un método periódicamente. Para esto es necesario que se introduzca varios elementos nuevos de la biblioteca base de clases de .NET Framework: System.Threading.TimerCallback, y, System.Threading.Timer.

1. Problema

Se requiere implementar una técnica programática para ejecutar un método de manera periódica. Esta ejecución debe realizarse sobre un thread de un pool de threads, de tal modo que no interrumpa el flujo de ejecución de tareas del thread principal.

2. Solución

La biblioteca base de clases de .NET Framework cuenta con dos artefactos (un delegado, y una clase) interesantes que solucionan el problema planteado: System.Threading.TimerCallback, y System.Threading.Timer. Esencialmente, estos son los pasos a seguir:
  1. Declaración del método que contiene el código a ejecutar periódicamente.
  2. Especificación de la firma del método debe ser compatible con la del delegado System.Threading.TimerCallback:

    - Retorno:
    void
    - Parámetro: object
  3. Crear una instancia de System.Threading.Timer y pasar como primer argumento una instancia de System.Threading.Callback. Además, los argumentos enteros para especificar los intervalos de tiempo.

3. Discusión de la Solución

Conozcamos a otro nivel de detalle cada uno de los artefactos planteadas en la sección Solución.

3.1 Delegado TimerCallback

El delegado TimerCallback (N:System.Threading) [2] encapsula un método que será ejecutado cada intervalo de tiempo por una instancia de Timer (discusión en la siguiente sub-sección). Esta es su firma:

public delegate void TimerCallback(Object state)

Descripción puntual:
  • Parámetro:
    • Object: contenedor de información útil para el método encapsulado en el delegado.
  • Tipo de retorno: void
Desde [2]: ~Este método no se ejecuta desde el método thread que crea la instancia Timer, en su lugar se crea un thread por es pasado sobre el pool de threads. Además, el delegado ejecutará el método que encapsula transcurrido una determinada cantidad de tiempo, y lo hará periódicamente cada intervalo de tiempo.

3.2 Clase Timer

La clase Timer [3] está contenida en el namespace System.Threading. Esta clase posee la maquinaria lógica para ejecutar un método (encapsulado por un delegado TimerCallback) en un intervalo de tiempo específico. La configuración de la declaración de clase no permite la herencia (sealed).

En la línea con lo anterior, en la Figura 1 se presenta el listado de constructores sobrecargados de Timer:
Lista sobrecargada de constructores de Timer

Para las instancias prácticas de esta clase utilizaré la versión sobrecargada [4]:

public Timer(TimerCallback callback, Object state, int dueTime, int perdio)

que consiste en la siguiente firma:
  • callback:TimerCallback: instancia de del delegado TimerCallback que contiene al método encapsulado que ha de ejecutarse periódicamente.
  • state:Object: instancia object con información útil para el método encapsulado.
  • dueTime:int: retraso (en milisegundos) antes de invocarse del callback.
  • period:int: intervalo de tiempo (en milisegundos) entre cada invocación del callback.

3.3 Ejemplo uso TimerCallback y Timer

En esta sub-sección presento (adaptado) el ejemplo de uso de estos dos artefactos para que se pueda percibir a nivel práctico su utilidad, y no quedarnos con el simple aperitivo teórico.

Archivo de código fuente UsoTimerCallbackTimer.cs [enlace alternativo]:
La clase VerificadorEstado se declara en la línea 7 y contiene la especificación declarativa para comprobar el estado de variables, presentar en pantalla su estado, y enviar señal una vez finalice la ejecución del método ComprobarEstado (líneas 20-35) a través del método Set de la clase AutoResetEvent.


Continuando, en la línea 44 se crea una instancia de AutoResetEvent que se utilizara para controlar la espera de ejecución periódica de un método. En la línea 47, se crea una instancia de VerificadorEstado con el argumento 10. Sobre la línea 50 creamos una instancia del delegado TimerCallback y encapsulamos el método ComprobarEstado. Enseguida (línea 55), se crea la instancia Timer a la que le pasamos como argumentos la instancia del delegado, el contenedor de información útil para el método encapsulado por el delegado la instancia de control de espera, el retraso en el inicio del delegado (1000), y, el intervalo de ejecución del delegado (250).



Con el método WaitOne invocado por la instancia AutoResetEvent en la línea 59, esperamos a que se genere la señal de finalización (con la línea 29) del método ComprobarEstado. En la línea 60, cambiamos el intervalo de ejecución a medio segundo.



Finalmente, en las líneas 65 y 66 se espera a que finalice nuevamente la ejecución del método ComprobarEstado, y con el método Dispose de Timer se cancela la ejecución periódica del método ComprobarEstado, respectivamente.


Compilación:


  1. csc /target:exe UsoTimerCallbackTimer.cs

Ejecución assembly:


  1. .\UsoTimerCallbackTimer.exe

Resultado:
Prueba de ejecución de UsoTimerCallbackTimer.exe
Figura 2. Prueba de ejecución de UsoTimerCallbackTimer.exe.

4. Práctica: Código Fuente C#

Sección práctica idónea para afianzar el conocimiento de la clase Timer y el delegado TimerCallback. Este ejemplo consiste en permitir al usuario de la aplicación cambiar el intervalo de ejecución del método encapsulado por el delegado. Cualquier valor inválido será percibido como valor centinela para cancelar la ejecución periódica.

Archivo de código fuente ControlEjecucionPeriodica.cs [enlace alternativo]:

Compilación:


  1. csc /target:exe ControlEjecucionPeriodica.cs

Ejecución assembly:


  1. .\ControlEjecucionPeriodica.exe

Resultado:
Prueba de ejecución de ControlEjecucionPeriodica
Figura 3. Prueba de ejecución de ControlEjecucionPeriodica.exe.

5. Conclusiones

La ejecución periódica de un método puede ser útil para aquellas aplicaciones que requieren realizar tareas con cierta periodicidad y sin la intervención del usuario, por ejemplo: eliminación de archivos temporales, limpiar memoria chaché, actualización de estados o registros, &c. Esta receta enseñó los esenciales conceptuales y prácticos para elaborar este tipo de tareas.

6. Glosario

  • Ejecución periódica
  • Intervalo de tiempo
  • Namespace
  • Pool de threads
  • Thread
  • Timer
  • TimerCallback

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]: TimerCallback Delegate (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.timercallback(v=vs.110).aspx
[3]: Timer Class (System.Threading) - http://msdn.microsoft.com/en-us/library/System.Threading.Timer(v=vs.110).aspx
[4]: Timer Constructor (TimerCallback, Object, Int32, Int32) (System.Threading) - http://msdn.microsoft.com/en-us/library/2x96zfy7(v=vs.110).aspx


M

No hay comentarios:

Publicar un comentario

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