Gestión de Timeouts con EventWaitHandle.

Trabajar con componentes – ya sea propios o externos - es algo muy habitual. En la mayoría de los casos  el componente se comporta como debe y todo parece funcionar correctamente.

Sin embargo, cada cierto tiempo … algo falla, recibimos la incidencia y nos ponemos manos a la obra. Pero no logramos reproducir el error. Una de las situaciones mas complejas en las que podemos encontrarnos es cuando el error se debe a que un componente emplea demasiado tiempo en realizar su trabajo. Es un problema difícil de reproducir, porque la demora puede ser debida a infinidad de motivos:

  • un exceso de carga de la máquina
  • un bloqueo sobre algún recurso
  • ejecución de un servicio externo …

Si nuestro programa es un servicio es todavía peor, ya que quien reportará el error será normalmente el cliente del servicio … O si el componente es cerrado … Es necesario disponer de un mecanismo que nos permita limitar el tiempo que nuestro programa para permitirnos actuar en consecuencia.

Vamos a intentar hacer un ejemplo, supongamos que trabajamos con el siguiente componente:

public class Component
    {
        public void ComponentProcess(int miliseconds, int delay) {
            var start = DateTime.Now ;
            
            Console.WriteLine("Inicio de ComponentProcess. Ms:{0}, delay:{1}", miliseconds, delay);
            Thread.Sleep(miliseconds*delay);
            var end = DateTime.Now ;
            Console.WriteLine("Inicio de Fin ComponentProcess. Tiempo de ejecucion(ms): {0}", end.Subtract(start) );
        }
    }

Este componente no hace nada, establece un tiempo de espera en función de sus dos parámetros. En realidad lo único que queremos es hacer variar el tiempo de ejecución del método para simular una situación imprevista.

Si utilizamos nuestro componente directamente(como hace el siguiente programa), podemos observar que el tiempo de ejecución va incrementándose (es lógico, es el comportamiento que hemos programado!)

class Program
    {
        public static void Main(string[] args)
        { 
            var a = new Component();            
            for (var i = 0; i <= 5; i++)
            {
                a.ComponentProcess(1000, i);
            }
            Console.WriteLine("Finaliza ejecucion del programa");
            Console.ReadLine();
        }
    }

El resultado de la ejecución del código anterior es este:

image

Como podemos ver el tiempo de ejecución tiende a infinito … comprometiendo la estabilidad y escalabilidad de nuestro sistema, y por supuesto el rendimiento.

Es mucho mejor poder establecer un límite de tiempo para la ejecución del componente, y poder actuar en consecuencia en lugar de esperar de forma indefinida a que el programa responda. Normalmente lanzando una excepción. Vamos a ello. Lo primero que vamos a hacer es definir las interfaces …

  • ITimeoutTask – Que representa una tarea con timeout.
  • ITaskResult – Que representa el resultado de la tarea.
public interface ITimeoutTask {        
    void Process(int timeout);
    ITaskResult Result { get; }
}

public interface ITaskResult {
    Exception Error {get;}
    bool Sucess {get;}
}

Ahora hay que realizar las implementaciones de las interfaces. Primero el resultado del proceso ITaskResult – tiene una propiedad «Sucess» que nos indica si la tarea se ha ejecutado correctamente y otra propiedad «Error» que nos permitirá acceder a la excepción en caso de error. Si nuestro proceso devolviera algún dato adicional deberíamos añadirlo a esta interfaz.

public class ComponentTaskResult : ITaskResult
{
    private Exception _error;
    private bool _sucess;

    public ComponentTaskResult(bool sucess, Exception error)
    {
        _sucess = sucess;
        _error = error;
    }

    public Exception Error
    {
        get { return _error; }
    }

    public bool Sucess
    {
        get { return _sucess; }
    }
}

Y ahora la clase que implementa ITimeoutTask , para la ejecución del componente. Para conseguir nuestro objetivo vamos a necesitar realizar la ejecución del componente en un nuevo thread y monitorizar el tiempo que emplea ese thread en ejecutarse(se explica mas adelante).

public class ComponentTask : ITimeoutTask 
{
    EventWaitHandle eventWait;
    Component componente;
    ITaskResult result;
    private int i_param = 0;
    private int b_param = 0;
   
    public ComponentTask (int ms, int delay  )
    {
        componente = new Component();
        i_param = ms;
        b_param = delay;
        eventWait = new EventWaitHandle(false, EventResetMode.AutoReset);
        result = new ComponentTaskResult(false, null);
    }

    public void Process(int timeout)
    {
        Thread hilo = new Thread(InternalTask);
        hilo.Start();            
        
        if (!eventWait.WaitOne(timeout))            
        {
            hilo.Abort();
            result = new ComponentTaskResult(false, new TimeoutException("TimeOut") );
        }

    }

    private void InternalTask()
    {
        try
        {
            componente.ComponentProcess(i_param, b_param);
            result = new ComponentTaskResult(true, null);       
        }
        catch (Exception ex)
        {
            result = new ComponentTaskResult(false, ex);
        }
        finally
        {
            eventWait.Set();
        }
    }

    public ITaskResult Result
    {
        get { return result; }
    }
}

En esta clase, tenemos definido un constructor con tantos parámetros como argumentos necesitamos para la utilización de nuestro componente (que es al fin y al cabo el objetivo que estamos persiguiendo) .Como hemos comentado vamos a necesitar realizar la ejecución del componente en un nuevo thread y sincronizar el proceso principal y secundario, por lo que instanciamos un objeto EventWaitHandle, que será el objeto encargado de monitorizar el tiempo ejecución y nos va a permitir sincronizar los procesos.

En la implementación de nuestra interfaz, en el método «Process» creamos un nuevo hilo para ejecutar el componente, y forzamos una espera de tiempo con el método WaitOne(int) del objeto EventWaitHandle. Este método, esperará al tiempo que hemos establecido o a que se realice la llamada al método Set() – que le indicará al proceso principal que se ha finalizado el trabajo. Si se llama al método Set() antes de que finalice el timeout el método WaitOne(int) devuelve true, y false en el caso de que el tiempo finalice. Por lo tanto, en el caso de que se produzca un timeout abortamos la ejecucion del hilo y establecemos la propiedad Result con el valor adecuado (ejecucion erronea y exception de tipo Timeout).

Ahora vamos a ver como modificamos el método Main para utilizar nuestra ITimeoutTask ….

class ProgramWithTimeout
    {
        public static void Main(string[] args)
        {
            var timeout = 3200; //3.2 segundos
            for (var i = 0; i <= 5; i++)
            {                
                var task = new ComponentTask(1000, i);
                task.Process(timeout);
                                              
                if (task.Result.Sucess)
                {
                    Console.WriteLine("OK, el proceso ha sido correcto");
                }
                else {
                    Console.WriteLine("Se ha producido un error. {0}", task.Result.Error.Message);
                }
            }
            Console.WriteLine("Finaliza ejecucion del programa");
            Console.ReadLine();
        }
    }

Dado que ahora la ejecución es asíncrona y con un timeout comprobamos el resultado del proceso a través de la propiedad Result, que nos indica el resultado y el error en caso de producirse.

La ejecución del programa es la siguiente:

image

De este modo podemos controlar la ejecución de nuestro componente, garantizando que aunque su comportamiento no sea el esperado nuestro programa si tendrá el comportamiento esperado.

Saludos,

DJK!

Pedro  Herrarte  Sánchez
Gestión de Timeouts con EventWaitHandle.
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:20/12/2011
Última actualizacion:20/12/2011
Visitas totales:3291
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com