lunes, 7 de abril de 2008

Excepciones en entorno asíncrono

Para mi primer post "serio", he decidido comentar brevemente el tema de las excepciones cuando estamos trabajando en entornos asíncronos. Y he escogido el tema porque hace muy poco tiempo, en la oficina un compañero me pidió ayuda cuando se estaba peleando con un tema muy similar.

Para los que no sepan muy bien que es trabajar con operaciones asíncronas, se podría decir que, resumiendo mucho, se trata de hacer que operaciones que suelen llevar mucho tiempo se ejecuten en segundo plano y permitan que la aplicación o el sistema puedan llevar a cabo otras tareas (típicamente mostrar información de progreso al usuario o permitirle realizar otras acciones en paralelo, etc.).En el siguiente enlace de la msdn se explica todo lo referente a la programación asíncrona :
Asynchronous Programming Design Patterns

El objetivo del post, es mostrar cómo se pueden capturar las excepciones en entornos asíncronos. La teoría es bastante sencilla, y basta con decir que en entornos asíncronos las excepciones se capturan siempre en el End o en el evento de retorno, pero a la hora de la verdad la cosa se complica un poco más. Para verlo más claro ilustraré cómo capturar una excepción en un típico caso de instrucción asíncrona (una llamada a un WebService) con un sencillo ejemplo:

Hemos definido un servicio Web que está disponible en una dirección cualquiera, y instanciamos la clase en nuestro código de la siguiente manera:

AsyncExampleWebService.AsyncExceptionWebService ws = new DevNetTips.AsyncExceptions.AsyncExampleWebService.AsyncExceptionWebService();

El servicio web tiene un método llamado ExceptionTest, al cual se le pasa un entero cómo parámetro. Si el parámetro és un 0 lanza una excepción.

Podríamos llamar al método de la siguiente manera:

ws.ExceptionTest(0); //llamada SÍNCRONA al método

Pero supongamos que es un método que puede tardar bastante en ejecutarse, y lo que queremos es que mientras estamos esperando el resultado, el usuario pueda seguir haciendo cosas. Para ello hay que usar el método:

ws.ExceptionTestAsync(param); //lamada Asíncrona al método.

Esto hará que la aplicación no se quede bloqueada hasta que el resultado sea devuelto, pero... como sabemos cuando ha acabado el método? En este caso (después veremos que hay maneras diferentes de llamar a operaciones asíncronas) se usa el evento definido en el servicio Web (hay que recordar que esto es automático, en el servicio únicamente se define el método ExceptionTest, ni el ExceptionTestAsync ni el evento).

El código quedaría así:

ws.ExceptionTestCompleted += new DevNetTips.AsyncExceptions.AsyncExampleWebService.ExceptionTestCompletedEventHandler(ws_ExceptionTestCompleted);
ws.ExceptionTestAsync(param);

void ws_ExceptionTestCompleted(object sender, DevNetTips.AsyncExceptions.AsyncExampleWebService.ExceptionTestCompletedEventArgs e)
{

}

El método ws_ExceptionTestCompleted se llama justo en el momento en el que el resultado del método ExceptionTest es devuelto. Este método tiene un evento de tipo ExceptionTestCompletedEventArgs que contiene toda la información relativa a la ejecución del método. Consultando la propiedad Result podemos saber lo que ha devuelto el método. ¿Pero que pasa si se produce una excepción?

Lo más instintivo supongo que sería intentar capturar la excepción al hacer la llamada al método ws.ExceptionTestAsync(param)

try{
ws.ExceptionTestAsync(param);
}
catch(Exception e)
{
//código de tratamiento de la excepción
}

Pero esto NO funciona. ¿Por qué? Porque estamos realizando una llamada a una operación asíncrona, y cómo hemos comentado un poco antes, las excepciones se capturan en el evento de retorno de la función.

Vemos que este caso concreto es muy sencillo, ya que únicamente se debe consultar el valor e.Error del parámetro ExceptionTestCompletedEventArgs del método de retorno. Si esta propiedad no es nula significa que se ha producido una excepción en la llamada al método. De una manera muy elegante se consigue capturar una excepción en una operación asíncrona. El código del método que captura el evento quedaría al final así:

void ws_ExceptionTestCompleted(object sender, DevNetTips.AsyncExceptions.AsyncExampleWebService.ExceptionTestCompletedEventArgs e)
{
if (e.Error == null)
{
Console.WriteLine(e.Result);
}
else
{
Console.WriteLine(e.Error.Message);
}
Console.WriteLine("");
}

Esta sencilla manera de hacer llamadas a operaciones asíncronas y capturar las posibles excepciones no es el único mecanismo que proporciona .Net. En lugar del modelo basado en eventos que hemos visto, existe el modelo basado en objetos IAsyncResult. Éste modelo (que a su vez se puede utilizar de varias maneras diferentes) permite muchas mas flexibilidad que el primero, aunque aumenta un poco su complejidad. Un sencillo ejemplo que muestra cómo hacer una llamada a un hostname ilustra una de los posibles maneras de hacer una llamada asíncrona y cómo capturar una posible excepción:

//llamada al método BeginGEtHostEntry. Inicio de la operación asíncrona. Se define un callback que se ejecutará cuando la ejecución del método hay finalizado
IAsyncResult result = Dns.BeginGetHostEntry("172.19.110.12", new AsyncCallback(DNSCallback), null);

//función de callback
private void DNSCallback(IAsyncResult result)
{
try
{
// se llama al método EndGetHostEntry. Final de la operación asíncrona.
//Aquí hay que capturar la posible excepción. La función devuelve el mismo tipo que la función síncrona
IPHostEntry host = Dns.EndGetHostEntry(result);
string[] aliases = host.Aliases;
IPAddress[] addresses = host.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases");
for (int i = 0; i <> 0)
{
Console.WriteLine("Addresses");
for (int i = 0; i <>
{
Console.WriteLine("{0}",addresses[i].ToString());
}
}
}
catch (SocketException e)
{
Console.WriteLine("An exception occurred while processing the request: {0}", e.Message);
}
Console.WriteLine("");
}


Para profundizar más en el uso de operaciones asíncronas y en concreto con IAsyncResult podeis visitar el siguiente link :
Calling Asynchronous Methods Using IAsyncResult


Descargar Código de los ejemplos


2 comentarios:

Anónimo dijo...

Muy practico y conciso. Enhorabuena.

Anónimo dijo...

Muy practico y conciso. Enhorabuena.