Gerardo Contijoch

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

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.

Una respuesta to “Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 2)”

  1. […] Hanselman « Sacar acentos, tildes y demás signos diacríticos de un texto Ejecutar tareas de forma asincrónica en ASP.NET 2.0 (Parte 2) […]

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

 
A %d blogueros les gusta esto: