martes, 20 de mayo de 2008

Clase Timeout c#

Cuando desde nuestro código hacemos llamadas a determinados métodos externos, podemos llegar a perder el control, si el método llamado no termina por cualquier causa nuestro código se queda 'colgado' en esa llamada. Hace poco tuve un problema de este tipo con Oracle. El Listener alcanzaba un estado tal que aceptaba las conexiones y te dejaba enganchado por horas...
En determinados procesos como por ejemplo un proceso de monitorización, puede ser crítico que nunca se produzca este enganche. Aquí va mi propuesta de lo que podría ser una librería para controlar el Timeout. Se trata de hacer la llamada en una segunda hebra y esperar un tiempo máximo para la consecución de la misma.
Se plantean dos problemas; el paso de parámetros y la captura de excepciones; La solución expone entre otras una clase estática que a su vez contiene cuatro métodos estáticos genéricos:

public static void RunV(int milisegundos, ThreadStart metodo)
public static T RunG<T, U>(int milisegundos, U par, Func<T, U> metodo)
public static T RunG<T, U, V>(int milisegundos, U paru, V parv, Func<T, U, V> metodo)
public static T RunG<T, U, V, W>(int milisegundos, U paru, V parv, W parw,Func<T, U, V, W> metodo)

tambien se definen tres delegados genéricos:

public delegate T Func<T,U>(U parametro);
public delegate T Func<T, U, V>(U parametrou, V parametrov);
public delegate T Func<T, U, V, W>(U parametrou, V parametrov, W parametrow);
además del archiconocido delegado void ThreadStart().

Con lo cual contempla llamadas a métodos void funcion() y métodos con retorno cualquier tipo con hasta tres argumentos de cualquier tipo.

Estoy de acuerdo que hacer un Abort de un Thread no es una opción muy recomendable, pero no es tan mala si la única alternativa que queda es ir al Task Manager y matar el proceso.

A continuación se muestra el código que implementa la librería:


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Timeouts
{
public delegate T Func<T,U>(U parametro);
public delegate T Func<T, U, V>(U parametrou, V parametrov);
public delegate T Func<T, U, V, W>(U parametrou, V parametrov, W parametrow);
internal class Envoltura
{
private Exception ex = null;
private ThreadStart metodo;
public void Run(ThreadStart metodo, int milisegundos)
{
this.metodo = metodo;
Thread t = new Thread(run);
t.Start();
bool final = t.Join(milisegundos);
if (!final)
{
t.Abort();
TimeoutSException to = new TimeoutSException();
to.Timeout = milisegundos;
throw to;
}
else
{
if (this.ex != null)
throw this.ex;
return;
}
}
private void run()
{
try
{
metodo();
}
catch (Exception e)
{
ex = e;
}
}
}
internal class Envoltura<T,U>
{
private Exception ex=null;
private U parametro;
private Func<T, U> metodo;
private T retorno;
public Envoltura(U parametro)
{
this.parametro = parametro;
}
public T Run(Func<T, U> metodo, int milisegundos)
{
this.metodo = metodo;
Thread t = new Thread(run);
t.Start();
bool final = t.Join(milisegundos);
if (!final)
{
t.Abort();
TimeoutSException to = new TimeoutSException();
to.Timeout = milisegundos;
throw to;
}
else
{
if (this.ex != null)
throw this.ex;
return this.retorno;
}
}
private void run()
{
try
{
this.retorno = metodo(parametro);
}
catch (Exception e)
{
ex = e;
}
}
}
internal class Envoltura<T, U, V>
{
private Exception ex = null;
private U parametrou;
private V parametrov;
private Func<T, U, V> metodo;
private T retorno;
public Envoltura(U parametrou, V parametrov)
{
this.parametrou = parametrou;
this.parametrov = parametrov;
}
public T Run(Func<T, U, V> metodo, int milisegundos)
{
this.metodo = metodo;
Thread t = new Thread(run);
t.Start();
bool final = t.Join(milisegundos);
if (!final)
{
t.Abort();
TimeoutSException to = new TimeoutSException();
to.Timeout = milisegundos;
throw to;
}
else
{
if (this.ex != null)
throw this.ex;
return this.retorno;
}
}
private void run()
{
try
{
this.retorno = metodo(parametrou, parametrov);
}
catch (Exception e)
{
ex = e;
}

}
}
internal class Envoltura<T, U, V, W>
{
private Exception ex = null;
private U parametrou;
private V parametrov;
private W parametrow;
private Func<T, U, V, W> metodo;
private T retorno;
public Envoltura(U parametrou, V parametrov, W parametrow)
{
this.parametrou = parametrou;
this.parametrov = parametrov;
this.parametrow = parametrow;
}
public T Run(Func<T, U, V, W> metodo, int milisegundos)
{
this.metodo = metodo;
Thread t = new Thread(run);
t.Start();
bool final = t.Join(milisegundos);
if (!final)
{
t.Abort();
TimeoutSException to = new TimeoutSException();
to.Timeout = milisegundos;
throw to;
}
else
{
if (this.ex != null)
throw this.ex;
return this.retorno;
}
}
private void run()
{
try
{
this.retorno = metodo(parametrou, parametrov, parametrow);
}
catch (Exception e)
{
ex = e;
}
}
}
public class TimeoutSException : Exception
{
private int timeout;
public int Timeout
{
get { return timeout; }
set { timeout = value; }
}
}
public class Timeout
{
public static void RunV(int milisegundos, ThreadStart metodo)
{
Envoltura env = new Envoltura();
env.Run(metodo, milisegundos);
}
public static T RunG<T, U>(int milisegundos, U par, Func<T, U> metodo)
{
Envoltura<T, U> env = new Envoltura<T, U>(par);
return env.Run(metodo, milisegundos);

}
public static T RunG<T, U, V>(int milisegundos, U paru, V parv,
Func<T, U, V> metodo)
{
Envoltura<T, U, V> env = new Envoltura<T, U, V>(paru, parv);
return env.Run(metodo, milisegundos);

}
public static T RunG<T, U, V, W>(int milisegundos, U paru, V parv,
W parw,Func<T, U, V, W> metodo)
{
Envoltura<T, U, V, W> env = new Envoltura<T, U, V, W>(paru, parv, parw);
return env.Run(metodo, milisegundos);

}
}
}


Y un programilla que la prueba:

using System;
using System.Collections.Generic;
using System.Text;
using Timeouts;
namespace Probe
{
public class Pruebas
{
public string Variable="";
public void metodov()
{
System.Threading.Thread.Sleep(500);
Variable += "hola,";
}
public string metodo(string par, string par2)
{
return par + par2;
}
public string metodo2(string par, string par2, int par3)
{
int i = 6 / par3;
return par + par2 + "-" + par3.ToString();
}
}
class Program
{
static void Main(string[] args)
{
//con void
Pruebas p = new Pruebas();
Timeout.RunV(1000, p.metodov);
Timeout.RunV(1000, p.metodov);
try
{
Timeout.RunV(100, p.metodov);
}
catch (TimeoutSException e) { Console.WriteLine("Timeout: " + e.Timeout.ToString() + " ms"); }
Console.WriteLine(p.Variable);
//inline con un parametro
string pp = Timeout.RunG<string, string>(10000, "pepe", delegate(string par)
{
string ret = par.ToUpper();
return ret;
});
Console.WriteLine(pp);
//otra prueba con dos parametros
string ret2 = Timeout.RunG<string, string, string>(1000, "Pp", "potamo", p.metodo);
Console.WriteLine(ret2);
//con tres
string st = Timeout.RunG<string,string,string,int>(1000, "Pp", "potamo",7, p.metodo2);
Console.WriteLine(st);
//las excepciones generadas en el delegado son capturadas.
try
{
Console.WriteLine(Timeout.RunG<string, string, string, int>(
1000, "Pp", "potamo", 0, p.metodo2));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
}

}
}

2 comentarios: