Sincronización de procesos lanzados al servidor WEB desde una aplicación WEB

Esta vez, les voy a relatar lo que me ocurrió, en mi trabajo desarrollando una aplicación WEB y el aprendizaje valioso que obtuve. Espero que esto sea de ayuda para otros y, especialmente, para aquellos quienes son mis lectores habituales.

Resulta, que para dar un poco de mayor amigabilidad al usuario, en su interacción con las aplicaciones WEB, se idearon lo que se denominó: procesamiento asíncrono. Este procesamiento asíncrono consiste en que cuando un proceso se lanza desde el cliente ( maquina del usuario) al servidor WEB, no se tiene que esperar a la respuesta del Servidor WEB, provocando de esta forma, que el usuario deba esperar al resultado del procesamiento. Antes de la invención de este tipo de procesamiento y de las tecnologías que lo permiten, el usuario, al dar click en un botón, por ejemplo, notaba como, momentáneamente, la página se volvía blanca y que se empezaban a pintar de nuevo todos los controles, textos e imágenes, que contenía, inicialmente, más la respuesta ocasionada como resultado del botón presionado. Esto, en cierta forma, era algo incomodo.

La invención del procesamiento asíncrono posibilitó, a las diferentes tecnologías que lo adoptaron, proporcionar la forma de marcar diferentes zonas de la página, para que del resultado de una acción lanzada por el usuario ( presionar un botón, seleccionar algo en una lista desplegable, etc), solamente las zonas marcadas se volvieran a pintar y, así, evitar pintar de nuevo toda la página, como ocurría antes. Esto posibilitó, a las aplicaciones WEB, mejorar su rendimiento (pues ya no se tenía que pintar la página completa, luego de acciones del usuarios) y su amigabilidad ( el usuario ya no veía esa página blanca, como antes, y podía seguir interactuando con la aplicación, mientras el servidor de forma asíncrona realizaba la acción ordenada).

Otras cosas interesantes, también, se permitieron a los desarrolladores de aplicaciones Web, con esto del procesamiento asíncrono, que les permitía trabajar de forma más eficiente, en procura siempre de la amigabilidad para el usuario. Por ejemplo, en el click de un botón, en el cliente, por medio de una función JavaScript, se podía ir de forma asíncrona y rápida, por cierta información al servidor y con esta, por ejemplo, realizar validaciones; esto evitaba el hecho de tener que ir al servidor y descubrir que la información no era adecuada y que, por tanto, se debía arrojar una excepción. La posibilidad de ir de forma asíncrona al servidor optimizó en gran medida el proceso y la interacción con el usuario.

Bueno, con este contexto, ahora sí vamos al tema central de este artículo. En mi caso, tenía el siguiente escenario:

1) Una Grid donde cada registro tenía un CheckBox.
2) Un botón Procesar.

El usuario chequeaba los registros que deseaba y presionaba el botón Procesar. En el botón Procesar, se procesaban sólo los registros chequeados.

Para dar amigabilidad al usuario, en este escenario, se adoptó, desde luego una tecnología para procesamiento asíncrono. Ahora, como la aplicación WEB se desarrollo con Visual Studio 2003, Framework 1.1, se investigó entre las existentes y se encontraron los controles Anthem aunque había otras como por ejemplo Magic Ajax. Cabe mencionar que actualmente existen muchas y mejores, como: AjaxPro, ASP.NET Ajax, etc.

Esta GRID, de Anthem, permitía que al dar click sobre un CheckBox, la respuesta fuera rápida y que solo el CheckBox que se chequeaba pareciera cambiar de no chequeado a chequeado (aunque, realmente, se pintaba de nuevo la GRID con el CheckBox chequeado). El comportamiento era lo esperado por el usuario y era amigable. Sin embargo, con el tiempo, hubo un incremento enorme de la información mostrada por la GRID y, por ello, el usuario empezó a notar que cuando daba click en un CheckBox debía esperar un pequeño momento, para que éste apareciera chequeado. El usuario solicitó que se mejorará el rendimiento, en el momento de chequeo de los CheckBox, por lo incomodo que resultaba y porque, como usuario, debía chequear de forma rápida muchos CheckBox y no podía perder tiempo esperando a que cada CheckBox apareciese como chequeado.

Para solucionar este inconveniente, se tenía que manejar la captura de los ítems chequeados en la GRID, en el cliente, por medio de JavaScript. Luego, cuando el usuario presionara el botón Procesar, se enviarían al servidor los ítems chequeados, para que, posteriormente, fueran procesados como hasta el momento se hacía. Es decir, a medida que se chequeaban ítems en la GRID se capturaba la información del ítem chequeado, por medio de JavaScript y en el evento click del botón Procesar, en el cliente, en una función JavaScript, se lanzaba un proceso asíncrono al servidor, para indicar cuales ítems habían sido chequeados, así:

function btnProcesar_Click(){
Anthem_InvokePageMethod('SubirDatosItemsSeleccionados',
[arregloRegsSeleccionados], function (result) {});
}

Para información, la sintaxis, arriba, corresponde a la forma como el Framework de los controles Anthem permiten ejecutar un llamado asíncrono a un método de la página en el servidor.
El método, en el servidor, tenía una forma como la siguiente:

[Anthem.Method]
public void SubirDatosSubmercadosSeleccionados(string arregloSubmercados)
{
//Proceso de subida de ítems seleccionados del cliente al servidor
}

Finalmente, en el manejador del evento Click, en el servidor, se realizaba el procesamiento de los registros seleccionados, como, hasta el momento, se hacía:

private void btnProcesar_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
//Procesamiento de los ítems seleccionados
this.btnAplicarAsociaciones.AutoUpdateAfterCallBack=true;
}

Se realizaron pruebas y la aplicación mostró un comportamiento errático, en unas ocasiones funcionaba correctamente y, en otras, no. Se realizó un estudio más profundo de lo que sucedía, por medio de la herramienta de Debug de Visual Studio y se encontró que el problema era que, unas veces, el programa entraba a ejecutar primero el código del servidor asociado al proceso lanzado de forma asíncrona (el método Anthem) y en segunda instancia el código del manejador de eventos del click en el servidor, en este caso, el programa funcionaba correctamente. No obstante, también se identificó que, en ocasiones, se ejecutaba primero el código del manejador del evento click en el servidor y en segunda instancia el del método Anthem y era, precisamente, en estas ocasiones, cuando el programa fallaba. Descubrir esta situación, nos llevó a concluir lo siguiente:

Cuando el usuario presionaba el botón Procesar, se lanzan al servidor dos procesos o tareas, para que éste llevara a cabo; uno asociado al proceso asíncrono de subir la información de los ítems seleccionados (P1) y otro asociado al manejo del evento click del botón (P2) y que, en unas ocasiones, se ejecutaba primero P1 y luego P2 que era el caso esperado, pero que, otras veces, se ejecutaba P2 y luego P1 presentándose el problema.

La solución era, entonces, sincronizar los procesos de tal forma que primero se ejecutara P1 y luego P2 y como se sabía que, en ocasiones, se ejecutaba primero P2, la idea era obligar a que antes de llevar a cabo el procesamiento de los ítems seleccionados se esperará a que el procesos P1 terminará completamente. Por tanto, se ideo la siguiente estrategia:

1) Crear una Propiedad, que permitía definir, en sesión, una indicación sobre si se había terminado el proceso de subida de ítems seleccionados:

private string EstaTerminadoProcSubirItems
{
get
{
return (string)HttpContext.Current.
Session["EstaTerminadoProcSubirItems"];
}
set
{
HttpContext.Current.Session["EstaTerminadoProcSubirItems"] = value;
}
}

2) Indicar, en el proceso P1, el momento, cuando se terminara completamente la subida de los ítems seleccionados:

[Anthem.Method]
public void SubirDatosSubmercadosSeleccionados(string arregloSubmercados)
{
this.EstaTerminadoProcSubirItems = "N";
//Proceso de subida de ítems seleccionados del cliente al servidor
this.EstaTerminadoProcSubirItems = "S";
}

3) En el proceso P2, procesar los ítems seleccionados, sólo cuando el valor de la propiedad fuera S, momento, en el cual, los ítems seleccionados ya se habían subido:

private void btnProcesar_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
long contador;
while(!this.EstaTerminadoProcSubirItems.Equals("S"))
{
contador++;
}
this.EstaTerminadoProcSubirItems = "";
//Procesamiento de los ítems seleccionados
this.btnAplicarAsociaciones.AutoUpdateAfterCallBack=true;
}

Aunque la idea era buena, no funcionó y no funcionó porque cuando se ejecutaba primero P2, la aplicación se quedaba en un Loop infinito, nunca encontraba el valor de la propiedad EstaTerminadoProcSubirItems en S. Esto nos hizo comprender que cuando se ejecutaba primero el proceso P2, el proceso P1 era puesto en espera por el servidor, para ser ejecutado sólo cuando se terminará el procesamiento del proceso P2, pero a P2 lo pusimos a esperar a que terminará primero P1, es decir, generamos un abrazo mortal.

Debido a esto, se intentó lograr la espera por el proceso P1, realizando este cambio en el proceso P2:

private void btnProcesar_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
while(!this.EstaTerminadoProcSubirItems.Equals("S"))
{
System.Threading.Thread.Sleep(500);
}
this.EstaTerminadoProcSubirItems = "";
//Procesamiento de los ítems seleccionados
this.btnAplicarAsociaciones.AutoUpdateAfterCallBack=true;
}


La idea era que el proceso P2 durmiera durante medio segundo y que, mientras tanto, se pudiera ejecutar el proceso P1. En ese momento, se pensaba que el hecho de colocar a dormir el proceso le permitiría al servidor ejecutar durante ese lapso el proceso P1, sin embargo, ello no ocurrió así y se seguía presentando el Loop infinito. Por casualidad, se vio la ayuda del método Sleep y se encontró que decía:

“Especifique cero (0) para indicar que este subproceso debe suspenderse de modo que permita que se ejecuten otros subprocesos en espera”

Por tanto, se hizo el cambio:

private void btnProcesar_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
while(!this.EstaTerminadoProcSubirItems.Equals("S"))
{
System.Threading.Thread.Sleep(0);
}
this.EstaTerminadoProcSubirItems = "";
//Procesamiento de los ítems seleccionados
this.btnAplicarAsociaciones.AutoUpdateAfterCallBack=true;
}

Y todo funcionó de forma correcta, se logró la sincronización de los procesos. Cuando se ejecutaba P2 y luego P1, P2 esperaba hasta que terminara P1. Resolviendo de esta forma el problema identificado.

Comentarios

Entradas populares de este blog

Visual Studio 2012 Backup and Restore bases de datos

Configuración de expresiones en Quartz

Hacer que Windows XP luzca como Windows 7