Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Posts Tagged ‘threading’

Utilidad de la propiedad IsReusable de la interface IHttpHandler

Posted by Gerardo Contijoch en mayo 20, 2009

Una de las propiedades, a mi parecer, menos documentadas del .NET Framework es la propiedad IsReusable de la interface IHttpHandler. La documentación nos indica que el valor de esta propiedad determina si el handler en cuestión puede ser reutilizado por más de un request en una aplicación web, lo cual es correcto, pero en ningún lugar dice porque o porque no deberíamos reutilizar un handler, ni cuales son las consecuencias de hacerlo. En realidad no es muy difícil imaginar una interpretación el valor de esta propiedad. Puede indicarnos si nuestro handler es thread safe o no, es decir, puede ser ejecutado concurrentemente en más de un thread sin que ello afecte su funcionamiento. Otra interpretación (ver comentarios de este post) igual de válida para mi, es que esta propiedad determina si se hace pooling del handler o no. Esto significa que si el handler el reutilizable, el mismo no es destruido una vez finalizado su uso, sino que es guardado en algún lado a la espera de otro request, evitando de ese modo, la reinstanciación innecesaria de handlers de uso muy frecuente.

Yo me inclino por la primera (aunque ello no quita que la segunda pueda ser válida también).

Como regla general, si nuestro handler guarda información (en una propiedad o un campo) referente a un request en particular, entonces la propiedad debería devolver false ya que otro request podría estar siendo procesado con la información incorrecta. Acá se puede ver un ejemplo del problema este.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Anuncios

Posted in Desarrollo Web | Etiquetado: , , | 2 Comments »

Problema con requests asíncronos en Silverlight 2

Posted by Gerardo Contijoch en febrero 28, 2009

Ayer fue uno de ‘esos días’ en el trabajo. Con ‘esos días’ me refiero a aquellos en los que uno pierde todo el día resolviendo (o al menos intentando resolver) un pequeñísimo inconveniente y no puede dar con la solución a pesar de estar convencido de que hay una. Uno esta a un paso de resolver todo, pero siempre hay un pero. En mi caso fue un pero grande, al punto en que no estoy seguro hasta donde vale la pena seguir el desarrollo de lo que estaba haciendo.

Espero que este post sirva de advertencia para los que deseen hacer algo similar a lo que hice yo y les ahorre bastantes horas de pruebas e investigación.

Lo que yo necesito hacer es una clase (dentro de una librería de clases Silverlight) que básicamente se comporte como un proxy a un WebService (cumple otras funciones este proxy, pero nos alcanza con saber que se conecta con un WebService). Como mi intensión es ocultar del usuario la verdadera implementación (es decir, la llamada al WebService en si), mi idea era consultar sincrónicamente este WebService cuando el usuario llame a un método y devolverle la respuesta en el momento, como si no hubiese WebService en el medio. Rápidamente encontré que no podía. Paso a explicar.

El problema está en como se comportan la clases HTTP (WebClient y HttpWebRequest en mi caso, pero lo mismo ocurre con los proxies a WebServices por ejemplo) en Silverlight.

Problema 1: Requests asincrónicos

En una aplicación WinForm o un site común las clases HTTP que vienen en el framework permiten hacer tanto requests sincrónicos como asincrónicos (por ejemplo, WebClient posee los métodos DownloadString y DownloadStringAsync), pero en Silverlight (que tiene una versión especial del Framework), solo se permite hacerlos de la última manera. Esto generó algunas quejas en la comunidad de programadores, pero parece que no va a cambiar ya que Microsoft se propuso respetar un estándar (NPAPI) que exige que estas llamadas sean hechas de manera asincrónica. Como Silverlight es un plugin para los browsers, el mismo necesita usar las APIS expuestas por este, las cuales están definidas por NPAPI (por lo menos en los browsers que respetan el estándar).

Así que me vi obligado a simular una operación sincrónica que por detrás funciona asincrónicamente. Para ello hice algo similar a esto (dejando de lado detalles como el manejo de excepciones y timeouts):

   1: public string Request() {
   2:
   3:     string respuesta = "";
   4:
   5:     WebClient client = new WebClient();
   6:
   7:     // Usamos este objeto para saber cuando se recibio la respuesta a nuestro request
   8:     AutoResetEvent arDownloadCompleto = new AutoResetEvent(false);
   9:
  10:     // Handler para procesar la respuesta
  11:     // Noten que nos limitamos a guardarla y a notificar que la recibimos
  12:     client.DownloadStringCompleted += delegate(object o, DownloadStringCompletedEventArgs ev) {
  13:         respuesta = ev.Result;
  14:         arDownloadCompleto.Set();
  15:     };
  16:
  17:     // Comenzamos el request (en este ejemplo, bajamos el HTML de www.example.com)
  18:     client.DownloadStringAsync(new Uri("http://www.example.com", UriKind.Absolute));
  19:
  20:     // Esperamos a que el método Set() sea llamado (esto deberia ocurrir cuando se complete la bajada)
  21:     arDownloadCompleto.WaitOne();
  22:
  23:     /* ...
  24:      * Procesamos la respuesta...
  25:      * ...
  26:      */
  27:
  28:     // Devolvemos la respuesta
  29:     return respuesta;
  30: }

La sincronicidad nos la da el objeto AutoResetEvent que en la línea 21 detiene la ejecución del hilo hasta que se haya completado la descarga.

Problema 2: Request solo en el UI Thread

Sin embargo este código no funciona debido a un pequeño detalle de la clase WebClient: el callback DownloadStringCompleted se ejecuta siempre en el UI Thread. En la mayoría de los casos eso es una buena noticia ya que podemos interactuar con la IU sin necesidad de estar usando el Dispatcher, pero en nuestro caso no lo es. Este comportamiento para nosotros significa que la ejecución de este método queda detenida en la línea 21, ya que nunca va a ejecutarse el callback debido a que el hilo esta ocupado deteniéndose a la espera del callback (sí, tiene sentido lo que dije, reléanlo).

Si ésa línea 21 hubiera sido la siguiente

// Esperamos 5 segundos como máximo para evitar un lockeo del hilo
arDownloadCompleto.WaitOne(5000);

hubiese continuado el proceso (luego de 5000 milisegundos), pero la variable respuesta hubiese estado vacía y el callback solo se hubiese ejecutado luego de que el UI Thread se haya liberado.

Como consecuencia de esto decidí darle una oportunidad a la clase HttpWebRequest (que, por cierto, es usada internamente por WebClient), que según leí no tiene esa limitación. Resulta sí la tiene, pero se presenta de otra manera. En el caso de HttpWebRequest, el callback se ejecuta efectivamente en un thread diferente al del código en el cual ejecutamos BeginGetResponse (una forma primitiva de DownloadStringAsync), pero ahora el problema no esta en el momento de ejecutar el callback, sino que esta al ejecutar el request en sí (algo que no vemos nosotros). Si bien uno puede crear hilos en Silverlight (y hacer requests asincrónicos en esos otros hilos), sólo el UI Thread es el que se comunica con el browser (¿recuerdan las APIs de NPAPI que expone el browser?) y por ello el request en sí se ejecuta en el UI Thread, independientemente de que thread vaya a ejecutar el callback. Una vez mas, tenemos un deadlock en la línea en que llamamos a WaitOne().

En definitiva, no encontré todavía manera de exponer una API sincrónica que funcione por dentro de modo asincrónico en Silverlight, pero si a alguien se le ocurre algo, no dude en dejar un comentario o contactarse conmigo.

Al mal tiempo, buena cara

Lo único positivo de perder un día entero con este tema es que aprendí mucho sobre el tema y sobre el funcionamiento interno de los requests, algo que me resulto interesantísimo (¡que nerd!), así que podría decirse que en realidad el día no estuvo del todo perdido :P

Nos vemos en el próximo post!

Referencias:

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Desarrollo Web, Silverlight | Etiquetado: , | 2 Comments »

Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 2)

Posted by Gerardo Contijoch en enero 30, 2009

En el post anterior hable sobre el método AddOnPreRenderCompleteAsync de la clase Page, el cual nos permite ejecutar una tarea de manera asincrónica en un hilo diferente al que esta procesando la página, pero como vimos entonces, este método tiene la desventaja de que sólo nos permite ejecutar una sola tarea y nada más. Hay muchos casos en los que precisamos realizar diferentes operaciones, no necesariamente relacionadas entre sí y para esos casos hay otra solución. Sugiero la lectura de la primera parte de esta serie ya que parte del código que voy a mostrar acá ya fue explicado allí.

La clase Page tiene un método que nos permite registrar tareas que deseamos que sean ejecutadas independientemente del procesamiento de la página. Estoy hablando de RegisterAsyncTask(). Este método acepta un objeto del tipo PageAsyncTask que contiene toda la información necesaria para poder lanzar las tareas y lograr que seamos notificados en su finalización. Estas tareas se van a ejecutar luego del evento PreRender y antes de que se produzca el evento PreRenderComplete (en la primer parte explico un poco el ciclo de vida de las páginas).

Al igual que con AddOnPreRenderCompleteAsync, es necesario agregar en la directiva Page la siguiente propiedad:

   1: <%@ Page Async="true" ... %>

Ahora, con todo listo no hay nada mejor que un ejemplo para entender su uso. Imaginemos que tenemos una página con un DropDownList y un GridView, los cuales se cargan independientemente el uno del otro. La carga de ambos podemos lograrla de la siguiente manera:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     private EventHandler ehRecuperarDatosDeGrillaPrincipal = null;
   4:     private EventHandler ehRecuperarDatosDeDropDownList = null;
   5:
   6:     // Datasets usados para guardar los datos a mostrar
   7:     private DataSet dsGrillaPrincipal;
   8:     private DataSet dsDropDownList;
   9:
  10:     protected void Page_Load(object sender, EventArgs e) {
  11:
  12:         // Creamos 2 tareas
  13:         PageAsyncTask pat1 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaPrincipal), new EndEventHandler(this.CargarGrillaPrincipal), null, null, true);
  14:         PageAsyncTask pat2 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeDropDownList), new EndEventHandler(this.CargarDropDownList), null, null, true);
  15:
  16:         // Registramos las 2 tareas para que se ejecuten asincrónicamente
  17:         Page.RegisterAsyncTask(pat1);
  18:         Page.RegisterAsyncTask(pat2);
  19:     }
  20:
  21:     IAsyncResult RecuperarDatosDeGrillaPrincipal(object sender, EventArgs e, AsyncCallback cb, object state) {
  22:         this.ehRecuperarDatosDeGrillaPrincipal = new EventHandler(this.RecuperarSetDeDatos1);
  23:         // Ejecutamos asincrónicamente el método que recupera o procesa los datos que necesitamos
  24:         return this.ehRecuperarDatosDeGrillaPrincipal.BeginInvoke(this, EventArgs.Empty, cb, null);
  25:     }
  26:
  27:     void CargarGrillaPrincipal(IAsyncResult ar) {
  28:         this.ehRecuperarDatosDeGrillaPrincipal.EndInvoke(ar);
  29:
  30:         this.GridView1.DataSource = this.dsGrillaPrincipal;
  31:         this.GridView1.DataBind();
  32:     }
  33:
  34:     IAsyncResult RecuperarDatosDeDropDownList(object sender, EventArgs e, AsyncCallback cb, object state) {
  35:         this.ehRecuperarDatosDeDropDownList = new EventHandler(this.RecuperarSetDeDatos2);
  36:         // Ejecutamos asincrónicamente el método que recupera o procesa los datos que necesitamos
  37:         return this.ehRecuperarDatosDeDropDownList.BeginInvoke(this, EventArgs.Empty, cb, null);
  38:     }
  39:
  40:     void CargarDropDownList(IAsyncResult ar) {
  41:         this.ehRecuperarDatosDeDropDownList.EndInvoke(ar);
  42:         this.DropDownList1.DataSource = this.dsDropDownList;
  43:         this.DropDownList1.DataBind();
  44:     }
  45:
  46:     private void RecuperarSetDeDatos1(object sender, EventArgs e) {
  47:         DataSet ds = new DataSet();
  48:         /*...*/
  49:         this.dsGrillaPrincipal = ds;
  50:     }
  51:
  52:     private void RecuperarSetDeDatos2(object sender, EventArgs e) {
  53:         DataSet ds = new DataSet();
  54:         /*...*/
  55:         this.dsDropDownList = ds;
  56:     }
  57: }

Debo aclarar que en el ejemplo hago uso del delegado EventHandler utilizado en la primera parte de esta serie debido a que estoy mostrando la forma más compleja de recuperar datos (normalmente es la única que usaremos). Las consultas de datos pueden ser realizadas dentro de los métodos RecuperarDatosDeGrillaPrincipal() y RecuperarDatosDeDropDownList(), pero es necesario devolver un objeto que implemente IAsyncResult, y como no tenemos ninguno en nuestro caso, lo creamos mediante el uso del delegado que mencioné. En la primera parte de esta serie se puede ver una de las maneras que hay para recuperar los datos directamente desde estos métodos.

Dos puntos a tener en cuenta. El primero es el tercer parámetro del constructor de PageAsyncTask, un EndEventHandler (igual que el segundo parámetro) que podemos usar en caso de que la tarea que ejecutemos lance un timeout (no se ve en el ejemplo). Este parámetro se configura en la directiva Page con la propiedad AsyncTimeOut (expresada en segundos) de la siguiente manera:

   1: <%@ Page Async="true" AsyncTimeout="20" ... %>

Por defecto este valor viene configurado a 45 segundos.

El otro punto a tener en cuenta es el último parámetro del mismo método, el cual nos permite especificar si deseamos que las tareas se ejecuten de forma secuencial o paralela. En el ejemplo, como las consultas son independientes, decidí ejecutarlas en paralelo.

Ahora veamos otro caso. Imaginemos que agregamos una nueva grilla a nuestra página y que los datos de la misma depende del valor seleccionado en el DropDownList. Esta tarea no podemos cargarla en el método Page_Load ya que necesitamos asegurarnos que la carga del DropDownList ha finalizado. Es por ello que podemos cargar esta tarea justamente en el momento de cargar la lista.

El siguiente ejemplo muestra como hacerlo y además muestra el uso de un EventHander propio para facilitar nuestra tarea:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     private EventHandler ehRecuperarDatosDeGrillaPrincipal = null;
   4:     private EventHandler ehRecuperarDatosDeDropDownList = null;
   5:     private RecuperarDatosDeGrillaSecundariaEventHandler ehRecuperarDatosDeGrillaSecundaria = null;
   6:
   7:     private DataSet dsGrillaPrincipal;
   8:     private DataSet dsDropDownList;
   9:
  10:     protected void Page_Load(object sender, EventArgs e) {
  11:
  12:         // Creamos 2 tareas
  13:         PageAsyncTask pat1 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaPrincipal), new EndEventHandler(this.CargarGrillaPrincipal), null, null, true);
  14:         PageAsyncTask pat2 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeDropDownList), new EndEventHandler(this.CargarDropDownList), null, null, true);
  15:
  16:         // Registramos las 2 tareas para que se ejecuten asincronicamente
  17:         Page.RegisterAsyncTask(pat1);
  18:         Page.RegisterAsyncTask(pat2);
  19:     }
  20:
  21:     IAsyncResult RecuperarDatosDeGrillaPrincipal(object sender, EventArgs e, AsyncCallback cb, object state) {
  22:         this.ehRecuperarDatosDeGrillaPrincipal = new EventHandler(this.RecuperarSetDeDatos1);
  23:         return this.ehRecuperarDatosDeGrillaPrincipal.BeginInvoke(this, EventArgs.Empty, cb, null);
  24:     }
  25:
  26:     void CargarGrillaPrincipal(IAsyncResult ar) {
  27:         this.ehRecuperarDatosDeGrillaPrincipal.EndInvoke(ar);
  28:         this.GridView1.DataSource = this.dsGrillaPrincipal;
  29:         this.GridView1.DataBind();
  30:     }
  31:
  32:     IAsyncResult RecuperarDatosDeDropDownList(object sender, EventArgs e, AsyncCallback cb, object state) {
  33:         this.ehRecuperarDatosDeDropDownList = new EventHandler(this.RecuperarSetDeDatos2);
  34:         return this.ehRecuperarDatosDeDropDownList.BeginInvoke(this, EventArgs.Empty, cb, null);
  35:     }
  36:
  37:     void CargarDropDownList(IAsyncResult ar) {
  38:         this.ehRecuperarDatosDeDropDownList.EndInvoke(ar);
  39:         this.DropDownList1.DataSource = this.dsDropDownList;
  40:         this.DropDownList1.DataBind();
  41:
  42:         this.DropDownList1.SelectedIndex = 0;
  43:
  44:         string id = this.DropDownList1.Items[0].Value;
  45:         // Cargamos la tarea y le pasamos el ID como parámetro
  46:         PageAsyncTask pat3 = new PageAsyncTask(new BeginEventHandler(this.RecuperarDatosDeGrillaSecundaria),
  47:                             new EndEventHandler(this.CargarGrillaSecundaria), null, id, true);
  48:
  49:         Page.RegisterAsyncTask(pat3);
  50:     }
  51:
  52:     IAsyncResult RecuperarDatosDeGrillaSecundaria(object sender, EventArgs e, AsyncCallback cb, object state) {
  53:         this.ehRecuperarDatosDeGrillaSecundaria = new RecuperarDatosDeGrillaSecundariaEventHandler(this.RecuperarSetDeDatos3);
  54:         return this.ehRecuperarDatosDeGrillaSecundaria.BeginInvoke((string)state, cb, null);
  55:     }
  56:
  57:     void CargarGrillaSecundaria(IAsyncResult ar) {
  58:         // El uso de un EventHandler personalizado nos permite devolver el DataSet
  59:         DataSet ds = this.ehRecuperarDatosDeGrillaSecundaria.EndInvoke(ar);
  60:         this.GridView2.DataSource = ds;
  61:         this.GridView2.DataBind();
  62:     }
  63:
  64:     private void RecuperarSetDeDatos1(object sender, EventArgs e) {
  65:         DataSet ds = new DataSet();
  66:         /*...*/
  67:         this.dsGrillaPrincipal = ds;
  68:     }
  69:
  70:     private void RecuperarSetDeDatos2(object sender, EventArgs e) {
  71:         DataSet ds = new DataSet();
  72:         /*...*/
  73:         this.dsDropDownList = ds;
  74:     }
  75:
  76:     private DataSet RecuperarSetDeDatos3(string id) {
  77:         DataSet ds = new DataSet();
  78:         /*...*/
  79:         return ds;
  80:     }
  81: }

La definición del RecuperarDatosDeGrillaSecundariaEventHandler es la siguiente:

   1: public delegate DataSet RecuperarDatosDeGrillaSecundariaEventHandler(string id);

Lo bueno del método RegisterAsyncTask es que hasta no terminar todas las tareas, no se retoma el procesamiento normal de la página por lo que podemos seguir agregando tareas si así lo quisiésemos. De todos modos, hay que tener en cuenta que al igual que con AddOnPreRenderCompleteAsync, estas tareas se ejecutan antes del evento PreRenderComplete, por lo que si las registramos luego de haberse producido ese evento, serán ignoradas por completo.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, ASP.NET | Etiquetado: , , , , | 1 Comment »

Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 1)

Posted by Gerardo Contijoch en enero 20, 2009

Navegando por los links de uno de los posts que me encontré hace un tiempo en mi feed reader, encontré un artículo interesantísimo de Jeff Prosise sobre ejecución asincrónica de páginas en ASP.NET 2.0 que no quería dejar de comentar. Ya hace tiempo que trabajo sobre el Framework 2.0, pero por algún motivo nunca llegué a leer algo sobre el tema (¡era hora que lo hiciera!). El artículo del que hablo es éste, y como no encontré mucho material en español sobre el tema voy a comentarles un poco de que trata el artículo y como lograr que las páginas en ASP.NET 2.0 se procesen (al menos en parte) de manera asincrónica.

Cabe aclarar que con las técnicas que voy a presentar nuestros sites no necesariamente se van a cargar más rápido de una manera perceptible para el usuario, sino que más bien se van a aprovechar los recursos para evitar tiempos muertos durante el procesamiento de las páginas.

Es necesario tener conocimiento sobre el ciclo de vida de las paginas en ASP.NET para poder entender como se logra la asincronicidad, por lo que voy a describir esto brevemente.

Ciclo de vida de una página en ASP.NET

Toda página tiene un ciclo de vida que comienza luego de que desde un cliente (como puede ser un browser) se hace una petición o Request por una página. A lo largo de este ciclo de vida se producen distintos eventos que nos van notificando por las etapas por las cuales esta pasando una página. Por ejemplo, el evento Init se produce luego de cargar los controles y es el momento de inicializarlos (setear sus propiedades). Por otro lado, el evento PreRender se da un instante antes de comenzar el renderizado de la página por lo que puede ser un buen lugar para modificar algún detalle de último momento relacionado con la visualización o renderizado de la página (yo tengo la costumbre de hacer visibles/invisibles o habilitar/deshabilitar ciertos controles en este punto y no antes, de modo que la lógica de presentación no se mezcle con la relacionada al binding de grillas, por ejemplo). Pueden encontrar el detalle del ciclo de vida de una página acá (a los más curiosos les recomiendo investigar el método ProcessRequestMain(bool, bool) de la clase Page con .Net Reflector).

El ciclo completo de una página es el siguiente:

  • PreInit
  • Init
  • InitComplete
  • LoadState (evento privado)
  • ProcessPostData (evento privado)
  • PreLoad
  • Load
  • ProcessPostData (una segunda vez)
  • “ChangedEvents” (eventos lanzados por los controles de la página)
  • PostBackEvents (evento privado)
  • LoadComplete
  • PreRender
  • PreRenderComplete
  • SaveState
  • SaveStateComplete
  • Unload

Cuando una página implementa la interface IHttpHandler, el ciclo de vida completo es ejecutado por un único hilo recuperado del ThreadPool, el cual es devuelto una vez finalizada la ejecución de la página. La mayoría de las paginas muestran datos que son procesados o recuperados de algún lado, ya sea una DB, un archivo de configuración, etc, y esa tarea casi siempre es independiente del procesamiento de la página. Si tardamos 5 segundos en recuperar los datos de una tabla de una DB, esos son 5 segundos de tiempo muerto en el que el hilo que esta ejecutando el procesamiento de la página se queda esperando los datos. Como el ciclo de vida de una página es necesariamente secuencial, no podemos aprovechar este tiempo muerto para procesar otras partes de la página, pero lo que si podemos hacer es liberar el hilo (es decir, devolverlo al ThreadPool) de modo que pueda ser reutilizado para el procesamiento de alguna otra tarea.

Ejecución asincrónica

Veamos como hacerlo. Lo primero es habilitar la ejecución asincrónica seteando el atributo Async en la directiva Page a true:

   1: <%@ Page Async="True" ... %>

Lo que esto produce es que la página implemente la interface IHttpAsyncHandler (a diferencia de las páginas ‘comunes’, que implementan IHttpHandler). No entiendo muy bien en dónde ni cómo ocurre esto ya que la clase Page implementa IHttpHandler y no se pueden cambiar sus interfaces así como así. Evidentemente todo este proceso ocurre antes de la propia compilación de la página (ya que luego de la misma, esta no puede ser modificada), pero no se exactamente en que punto (si alguien sabe, que diga).

En fin, me estoy llendo por las ramas. Al implementar esta interface, vamos a poder introducir dos eventos nuevos en el ciclo de vida de la página. Estos se van a ejecutar inmediatamente después del evento PreRender y antes de PreRenderComplete . El primero de ellos lo que va a hacer es ejecutar una tarea de manera asincrónica devolviendo un objeto que implemente IAsyncResult. El segundo, va a ser lanzado cuando el proceso asincrónico finalice, retomando ahí lo que queda del procesamiento de la página.

Para atrapar estos eventos vamos a hacer uso del método AddOnPreRenderCompleteAsync de la clase Page:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:     protected void Page_Load(object sender, EventArgs e) {
   3:         Page.AddOnPreRenderCompleteAsync(
   4:             new BeginEventHandler(this.BeginAsync),
   5:             new EndEventHandler(this.EndAsync)
   6:         );
   7:     }
   8:
   9:     IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback cb, object state) {
  10:         /*...*/
  11:     }
  12:
  13:     void EndAsync(IAsyncResult ar) {
  14:         /*...*/
  15:     }
  16: }

Debido a que los eventos estos son lanzados antes del evento PreRenderComplete, es necesario que este método sea ejecutado antes de llegar a esta fase del procesamiento de la página.

Veamos ahora como recuperar datos para cargar una grilla de manera asincrónica:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     SqlCommand cmd;
   4:
   5:     protected void Page_Load(object sender, EventArgs e) {
   6:        Page.AddOnPreRenderCompleteAsync(
   7:            new BeginEventHandler(this.RecuperarDatos),
   8:            new EndEventHandler(this.CargarGrilla)
   9:        );
  10:     }
  11:
  12:     IAsyncResult RecuperarDatos(object sender, EventArgs e, AsyncCallback cb, object state) {
  13:        SqlConnection cnn = new SqlConnection("...");
  14:        cnn.Open();
  15:
  16:        this.cmd = new SqlCommand("SELECT * FROM ...", cnn);
  17:
  18:        // Atencion con el CommandBehavior, si no lo usamos de esta forma, necesitamos mantener una
  19:        // referencia a la conexión a nivel de clase para poder cerrarla despues!
  20:        return cmd.BeginExecuteReader(cb, state, CommandBehavior.CloseConnection);
  21:     }
  22:
  23:     void CargarGrilla(IAsyncResult ar) {
  24:        SqlDataReader reader = cmd.EndExecuteReader(ar);
  25:        this.GridView1.DataSource = reader
  26:        this.GridView1.DataBind();
  27:        reader.Close();
  28:     }
  29: }

Esta técnica puede aplicarse también a la ejecución de WebServices ya que los mismos ofrecen una forma Begin[WebMethod] y End[WebMethod] para cada uno de los métodos que se expongan.

Sin embargo, hay ocasiones en lo que deseamos hacer es algo más complejo que una simple consulta o simplemente el método ExecuteReader no nos sirve. En esos casos podemos hacer algo como lo siguiente:

   1: public partial class AsyncForm : System.Web.UI.Page {
   2:
   3:     // Referencia al EventHandler que vamos a usar para llamar al método que recupera los datos
   4:     private EventHandler ehRecuperarDatos = null;
   5:     // Aca vamos a guardar los datos que recuperemos
   6:     private DataSet dsDatos = null;
   7:
   8:     protected void Page_Load(object sender, EventArgs e) {
   9:
  10:         Page.AddOnPreRenderCompleteAsync(
  11:             new BeginEventHandler(this.RecuperarDatos),
  12:             new EndEventHandler(this.CargarGrilla)
  13:             );
  14:     }
  15:
  16:     IAsyncResult RecuperarDatos(object sender, EventArgs e, AsyncCallback cb, object state) {
  17:
  18:         // Esto es necesario porque tenemos que devolver un IAsyncResult
  19:         this.ehRecuperarDatos = new EventHandler(this.RecuperarDataSet);
  20:         return this.ehRecuperarDatos.BeginInvoke(this, EventArgs.Empty, cb, null);
  21:     }
  22:
  23:     private void RecuperarDataSet(object sender, EventArgs e) {
  24:         DataSet ds = new DataSet();
  25:         /*...*/
  26:         this.dsDatos = ds;
  27:     }
  28:
  29:     void CargarGrilla(IAsyncResult ar) {
  30:         this.ehRecuperarDatos.EndInvoke(ar);
  31:         this.GridView1.DataSource = this.dsDatos;
  32:         this.GridView1.DataBind();
  33:     }
  34: }

En el ejemplo estoy usando un EventHandler genérico, pero tranquilamente podríamos crear nuestro propio EventHandler con parámetros propios y usarlo para facilitarnos la tarea de recuperación y/o procesamiento de los datos.

Como se ve, no es muy difícil ejecutar tareas en segundo plano, pero éste método nos limita a sólo una única tarea. En un próximo post voy a mostrar otro método para ejecutar más de una tarea asincrónicamente.

[Update: Pueden encontrar la segunda parte de este artículo acá.]

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, ASP.NET | Etiquetado: , , , , | 1 Comment »