viernes, 20 de febrero de 2015

Hilo de Ejecucion - BackgroundWorker

Cascaron DataLayerIng Cuando realizamos un aplicación es muy importare que esta no se quede pasmada si es que lanzamos una proceso muy pesado o tardado de realizar; ¿les ha pasado que sale diciendo la aplicación no responde ? y no por que su aplicación realmente de haya trabado es que el proceso es muy tardado y corre sobre el hilo principal de la aplicación lo cual parece dejarla trabada hasta que este proceso termine; ¿Como solucionarlo? con la complementación de  hilos de ejecución conocidos como Therads.

Hay muchas formas de implementar hilos pero la mas sencilla es usando el componente BackgroundWorker este fue introducido desde el framework 2.0 y permite realizas operaciones costosas o duraderas en un hilo diferente al de la interfaz por lo que nuestra aplicación no se vera afectada y continuara respondiendo.

Usando este control, la gestión de hilos está encapsulada en el control de manera que el no tenemos que lidiar con hilos (threads), invokes o delegados (delegates). Que suelen hacer la vida un poco complicada.

Ahora bien se puede usar el componente BackgroundWorker arrastrándolo de la paleta:

El componente tiene 3 funciones para controlar el funcionamiento:

Evento DoWork

Este es invocado cuando se ejecuta le función RunWorkerAsync() en ese momento se genera el segundo hilo de ejecucion; y dado que no es el hilo principal pues la aplicación segira respondiendo como si nada; es decir como lo dice le nombre de la función corre asincronamente por lo que es importante que se tome en cuenta que corre de forma asincrona por lo que el codigo no esperará el aproceso para continuar:

private void button1_Click(object sender, EventArgs e)
        {
            progressBar1.Visible = true;
            backgroundWorker1.RunWorkerAsync();   //este se llama y se ejecuta
            button1.Enabled = false;                               //Contiua esta instruccion sin esperar el hilo
            button2.Enabled = false;
}
Es importaten señalar cuando estemos codificando el DoWork que tomemos en cuenta que el valor  que regresamos queda guardado en la propiedad e.Result el cual podremos invocar despues  cuando el proceso este completo; en este ejemplolos uso para regresar el resultado de una consulta ala base dedatos que llega a demorar algún tiempo, y como se ve el resultado de la consulta lo almaceno en e.Result

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
 List queryResult = db.COMPROBANTE.Where(w => w.C_ENVIO == 0 && w.C_ENVIO != null).ToList();//&& w.CANCELA.Count < 0)
 var query = queryResult.Select(s => new DataBindingProjection
 {
  ID = s.C_ID,
  Cliente = s.RECEPTOR.R_NOMBRE,
  Correo = s.RECEPTOR.R_CORREO,
  SERIE = s.C_SERIE,
  FOLIO = s.C_FOLIO,
  FolioFiscal = s.C_FOLIOFISCAL,
  SubTotal = s.C_SUBTOTAL,
  Iva = s.C_IVA,
  Total = s.C_TOTAL,
  Saldo = decimal.Round((decimal)s.C_SALDO, 2),
  TIPOCAMBIO = s.C_TIPOCAMBIO,
  Moneda = s.C_TIPOCAMBIO == 1 ? "MXN" : "USD",
  FechaEmicion = s.C_FECHA,
  FechaVencimiento = s.C_VENCIMIETO
 }
 ).OrderBy(o=>o.SERIE).ThenBy(o=>o.FOLIO).ToList();
 e.Result = query.ToList();//regresa el arreglo oque voy a desplegar

}

Evento ProgressChanged

Este evento es lanzado en el hilo principal, por lo que aquí SI podemos acceder a controles del formulario de manera segura.

private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
   progressBar1.Value = e.ProgressPercentage; //actualizamos la barra de progreso
}

Evento RunWorkerCompleted


Este método se ejecuta cuando la el proceso ha concuido junto con el hilo de ejecuciony solo peude ser por 3 medio:


  1. El proceso termina de forma normal sin errores ni cancelaciones
  2. El proceso termina debido a un error durante su ejecucion
  3. Cancelado por el usuario/programador

En el objeto RunWorkerCompletedEventArgs nos brinda la información de como es que se completo el hilo y asi saber como trabajar con el valor de la propiedad Result,


private void backgroundWorker1_RunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
   if (e.Cancelled) {
     MessageBox.Show("La tarea fue cancelada");
   }
   else if (e.Error != null)
   {
     MessageBox.Show("Setermino con error: " + (e.Error as Exception).ToString());
   }
   else {
     MessageBox.Show("La busqueda a concluido ");
  if (e.Result != null)
  {
   dataGridView1.DataSource = e.Result;
   progressBar1.Visible = false;
   button1.Enabled = true;
   button2.Enabled = true;
  }

   }
}



Enviado las órdenes de cancelación al control.


Por ultimo pero no menos importante como podemos cancelar el proceso una ves que ha sido lanzado? pues es facil solo se invoca la funcion CancelAsync() y el proceso terminará

private void btoCancel_Click(object sender, EventArgs e)
{
   backgroundWorker1.CancelAsync();
}


En fin creo que eso es todo ojala les resulte útil is tienen dudas pregunten abajo...

5 comentarios:

  1. He revisado varias páginas acerca de este tema y ha sido en esta donde realmente encontré la aclaración concisa a todas mis dudas. Mil felicidades.

    ResponderEliminar
  2. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  3. Primero agradecerte por el gran aporte que nos brindas sobre este tema. Seguido quería saber como puedo llenar un DataGridView con una lista de filas obtenidas de una consulta sql y que me muestra el progreso en un progressbar, he tratado me sale error: "Información adicional: Operación no válida a través de subprocesos: Se tuvo acceso al control 'dgvListadoArticulos' desde un subproceso distinto a aquel en que lo creó."
    Si me puedes ayudar te dejo mi codigo. Muchas gracias!

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
    _articulo = new Articulos();
    dgvListadoArticulos.AutoGenerateColumns = false;
    dgvListadoArticulos.DataSource = _articulo.listarArticulos();
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    pbBarra1.Value = e.ProgressPercentage;
    }

    private void btnEjecutarBGW_Click(object sender, EventArgs e)
    {
    pbBarra1.Visible = true;

    backgroundWorker1.RunWorkerAsync();

    btnEjecutarBGW.Enabled = false;

    button2.Enabled = false;
    }

    ResponderEliminar
  4. Excelente tu información, estaba quemandome las pestañas con threads jajaja

    ResponderEliminar