Una de las cosas que más quebraderos de cabeza nos pueden ocasionar a todos nosotros es la concurrencia de usuarios en las aplicaciones que diseñemos. Por suerte, además de conocer los algoritmos más típicos (Panadería, Dekker, Patterson, Productor-Consumidor…), .NET nos facilita bastante la implementación de esta tarea.
Hace pocos días en un diseño técnico de una aplicación Web, el cliente nos plantaba un proceso crítico que tendría la aplicación y que debería ser estable pasase lo que pasase. Además, con una dificultad añadida, el proceso podía durar horas y al finalizar debería mostrar un mensaje al usuario.
Dado esto, encontré la solución más viable para el proceso: trabajar en segundo plano sincrónicamente.
Para ello, deberemos hacer uso de los delegados y de las librerías System.Threading, System.Runtime.Remoting.Messaging y de System.Runtime.CompilerServices.
Como primer paso, además de crear una nueva aplicación de consola en C#, deberemos crear una clase con un delegado:
public class ClassAsyncCallback
{
delegate bool deleg();
}
El segundo paso, es crear los métodos que se van a usar en el proceso síncrono dentro de la clase; uno que llamará a la función que va a realizar el trabajo y otro que será llamado al terminar el primer proceso:
[MethodImpl(MethodImplOptions.Synchronized)]
public bool ProcessCritial()
{
//Ejecutamos el proceso
}
Acordaros de que un delegado es tipo que hace referencia a un método, es decir, el comportamiento del delegado y el método es exactamente el mismo y además, este debe tener la misma firma, valor devuelto, parámetros…que el método.
En este método, aparece algo nuevo, es el atributo declarativo en la declaración del método [MethodImpl(MethodImplOptions.Synchronized)]. Este atributo sirve para indicar que ese método estará sincronizado y que todo lo implementado en él es un proceso crítico. Funciona del mismo modo que la clase Monitor, internamente, declara un semáforo que gestiona la entrada única de un subproceso en él método y pone en espera al resto. Al finalizar, libera el método y deja entrar al siguiente proceso. Así sucesivamente.
El motivo de utilizar este atributo en vez la clase Monitor es muy simple, este atributo bloquea el método hasta que devuelve un valor, que en mi caso es lo que necesitaba, además que Monitor nos condiciona a usar además lock para el bloqueo de una variable para saber si el método esta libre o no y como bien podéis deducir, no es una buena practica.
Bien, ahora solo nos queda crear el método que mostrara el mensaje al usuario:
public void ProcessFinish(IAsyncResult ar)
{
//Mostramos alerta al usuario
}
Como veis, este proceso recibe como parámetro IAsyncResult que nos indica el estado de la operación asíncrona.
Por ahora, nos centraremos en como llamar de forma asíncrona estos métodos des del Main de la aplicación:
static void Main(string[] args)
{
ClassAsyncCallback Mvar = new ClassAsyncCallback();
deleg call = new deleg(Mvar.ProcessCritial);
AsyncCallback cb = new AsyncCallback(Mvar.ProcessFinish);
IAsyncResult ar = call.BeginInvoke(cb, null);
}
Bien, llegados hasta aquí ya tenemos la forma de ejecutar el proceso asíncrono preparado para la concurrencia de usuarios. Como veis, deberemos crear una nueva instancia del delegado declarado anteriormente y pasarle el nombre del método. Con la clase AsyncCallback y pasándole el nombre del método, ya tendremos el objeto que realizara la llamada.
Mediante IAsyncResult y con BeginInvoke, ejecutaremos el proceso asíncrono. Como primer parámetro le pasaremos el método que se debe llamar al finalizar y con el objeto delegado llamaremos el método que debe ser llamado asincrónicamente.
Tal y como hemos dicho, ProcessFinsih es el encargado de mostrar la alerta al usuario según el resultado del método ProcessCrtical. Para recoger el valor devuelto por este dentro de ProcessFinsih, debemos modificar el método de la siguiente forma:
public void ProcessFinish(IAsyncResult ar)
{
deleg resultado = (deleg)((AsyncResult)ar).AsyncDelegate;
if (resultado.EndInvoke(ar))
//Mostramos alerta al usuario
}
Con AsyncDelegate lo que hacemos es recoger el objeto delegado que se ha invocado en la llamada asíncrona y se debe convertir al tipo de delegado declarado por nosotros para poder obtener el resultado de ProcessCrtical; si es true le mostraremos la alerta al usuario.
No es muy complicado ni nada dificultoso, hay que decir que este tipo de problemas es muy típico en bastantes aplicaciones, sobretodo si hay procesos que requieran una gran cantidad de tiempo. Además de solventar un posible problema de concurrencia de usuarios, evitamos que nuestro cliente tenga que esperar x tiempo a que termine este proceso para seguir trabajando con la aplicación por lo que mejoramos el rendimiento de la misma.
Pues nada, a disfrutarlo y ya sabéis… si hay dudas, preguntar.
¡¡Enjoy!!