WPF – Procesos en segundo Plano (BackgroundWorker)


Muchas veces, necesitamos realizar tareas, como descargar archivos, invocación de Servicios Web, Transacciones de base de datos, y otras que consumen una enorme cantidad de tiempo. Ejecutando estas tareas en hilos separados permiten que se mantenga una respuesta en la interface del usuario mientras se completa la tarea.

El componente BackgroundWorker es el recomendado para ejecutar tareas asincrónicas o en segundo plano, cuando el hilo en segundo plano necesita interactuar con el hilo de la interfaz del usuario.

El método clave del BackgroundWorker es el RunWorkerAsync. Cuando este método es llamado, el BackgroundWorker dispara el evento DoWork

 Actualización 26-10-2009

El componente BackgroundWorker también se encuentra disponible para aplicaciones WindowsForms en .Net:

BackgroundWorker

Corriendo un Proceso en segundo plano:

  1. Se declara en el código una nueva instancia del BackgroundWorker:
  2. System.ComponentModel.BackgroundWorker bgw = new System.ComponentModel.BackgroundWorker();
    
  3. En el constructor, agregamos el manejador de eventos para el evento DoWork
  4. bgw.DoWork += new System.ComponentModel.DoWorkEventHandler(bgw_DoWork);
    
  5. Crear el manejador de eventos para el evento RunWorkerCompleted:
  6. bgw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
    
  7. Se crea el evento DoWork y aquí es donde haremos el código que se ejecutara en segundo plano:
  8. void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
    // hacer algo
    }
    
  9. En el evento creado bgw_RunWorkerCompleted implementamos el código que queremos que se muestre al finalizar la operación:
  10. void bgw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
     if (e.Error != null)
      {
       MessageBox.Show(e.Error.Message);
      }
     else if (e.Cancelled)
      {
       resultadoLabel.Content = "Cancelado";
      }
     else
      {
       resultadoLabel.Content = "Operación Finalizada";
      }
    }
    
  11. Crear un método donde inicialicemos la operación en segundo plano, llamando el método RunWorkerAsync
private void ejecutarTransaccion()
{
   bgw.RunWorkerAsync();
}

Enviar parámetros al proceso:

Muchas veces el proceso que se ejecuta, requiere uno o más parámetros, como la dirección del archivo a descargar, el nombre de la base de datos, etc. Se pueden enviar dichos parámetros en el método RunWorkerAsync que estarán en la propiedad Argument de la instancia de DoWorkEventArgs:

  1. Incluir el parámetro en el llamado del RunWorkerAsync:
  2. bgw.RunWorkerAsync("http://www.tailspintoys.com/sample.xml">http://www.tailspintoys.com/sample.xml);
    
  3. Obtener los parámetros de la propiedad DoWorkEventArgs.Argument y convertirlos apropiadamente para usarlos en el proceso:
String direccion;
direccion = (String)e.Argument;
documento = new XmlDocument();
documento.Load(direccion);
Thread.Sleep(5000);

Si el proceso requiere más que un parámetro, es necesario usar una colección o un array de objetos.

Retornando valores de un Proceso en segundo Plano:

Si nuestra aplicación esta haciendo una operación muy compleja, y nosotros necesitamos saber el resultado de la operación al finalizar la ejecución, es necesario enviar dicho resultado al finalizar la operación, podemos enviar el resultado de la operación en la propiedad Result del evento DoWork para luego ser obtenida en la misma propiedad pero en el evento RunWorkerCompleted

void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  e.Result = CalculoComplejo();
}
void bgw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
   object result;
   result = e.Result
 }

Cancelando un proceso en segundo plano:

Es necesario en aplicaciones que tardan mucho, tener la habilidad de que el usuario cancele la operación cuando lo desee, para hacer esto debemos indicarle al BackgroundWorker que soporte la cancelación, dejando en true la propiedad WorkerSupportsCancellation, luego llamar el método CancelAsync para intentar cancelar la operación, al hacer esto la propiedad CancellationPending queda en True y podemos determinar que la operación ha sido cancelada.

  1. Dejar en True la propiedad WorkerSupportsCancellation:
  2. bgw.WorkerSupportsCancellation = true;
    
  3. Crear un método que cancele la operación:
  4. private void button2_Click(object sender, RoutedEventArgs e)
    {
      bgw.CancelAsync();
    }
    
  5. En el evento DoWork, preguntar por la propiedad CancellationPending y si es True, implementar el código de cancelación del proceso
void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
if (bgw.CancellationPending)
{
  e.Cancel = true;
  return;
}
}

Mostrar el progreso de un proceso en segundo plano:

Para mostrar al usuario el progreso de la operación, y saber cuánto le falta, se llama el método ReportProgress, este método invoca el evento BackgroundWorker.ProgressChanged y permite pasar un parámetro que indique el porcentaje que se ha completado.

Antes de hacer eso, hay que dejar la propiedad WorkerReportsProgress en true.

  1. Dejar en True la propiedad WorkerReportsProgress en true
  2. bgw.WorkerReportsProgress = true;
    
  3. Crear el manejador de eventos ProgressChanged:
  4. bgw.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(bgw_ProgressChanged);
    
  5. En el evento DoWork, implementar el código que calcule el progreso y se envié al método ReportProgress:
  6. void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
    for (int i = 1; i <= 100; i++)
      {
         Thread.Sleep(200);
         bgw.ReportProgress(i);
       }
    }
    
  7. En el evento ProgressChanged creado, mostrar en un label u otro control el progreso:
void bgw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
  label2.Content = e.ProgressPercentage.ToString();
  progressBar1.Value = e.ProgressPercentage;
}

Ejemplo: Descargar un XML de Internet

Vamos a crear una aplicación en WPF, donde descargamos un XML y luego lo mostramos en un texto, aquí vamos a usar todo lo aprendido anteriormente.

La interfaz de Usuario que tengo es la siguiente:

 BGW1

Tenemos:

–          Un TextBox para ingresar la dirección Web del xml.

–          Dos botones, uno para iniciar la descarga y otro para cancelarla

–          Un ProgressBar para mostrar el progreso

–          Un label para mostrar el progreso en porcentaje

–          Un TextBlock donde vamos a mostrar el resultado del xml

Como ya se explico anteriormente cada concepto, se va a mostrar todo el código y las imágenes del ejemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Threading;
 
namespace BackgroundWorker
{
    /// <summary>
    /// Lógica de interacción para descargar un XML
    /// </summary>
    public partial class Window1 : Window
    {
        System.ComponentModel.BackgroundWorker bgw = new System.ComponentModel.BackgroundWorker();
        public Window1()
        {
            InitializeComponent();
            bgw.WorkerSupportsCancellation = true;
            bgw.WorkerReportsProgress = true;
            bgw.DoWork += new System.ComponentModel.DoWorkEventHandler(bgw_DoWork);
            bgw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
            bgw.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(bgw_ProgressChanged);
        }
        void bgw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            label2.Content = e.ProgressPercentage.ToString() + "%";
            progressBar1.Value = e.ProgressPercentage;
        }
        void bgw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else if (e.Cancelled)
            {
                MessageBox.Show("Ejecución Cancelada");
            }
            else
            {
                MessageBox.Show("Ejecución Completada");
                txbResultado.Text = e.Result.ToString();
                this.button1.IsEnabled = true;
            }
        }
        void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            //Thread.Sleep(5000);
            String direccion;
            direccion = (String)e.Argument;
            for (int i = 1; i <= 100; i++)
            {
                Thread.Sleep(200);
                bgw.ReportProgress(i);
                if (bgw.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
            }
            e.Result = descargarXML(direccion);
        }
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            if (!String.IsNullOrEmpty(txtDireccion.Text))
            {
                bgw.RunWorkerAsync(txtDireccion.Text);
                this.button1.IsEnabled = false;
            }
            else
            {
                MessageBox.Show("¿Que va a descargar?, ingrese la URL o Dirección del XML");
            }
        }
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            bgw.CancelAsync();
            this.button1.IsEnabled = true;
        }
        private String descargarXML(String URL)
        {
            XmlDocument documento;
            documento = new XmlDocument();
            documento.Load(URL);
            return documento.InnerXml;
        }
    }
}

 BGW2

BGW3

En resumen vamos a describir los miembros más importantes del componente BackgroundWorker:

Miembros usados por el componente BackgroundWorker:

CancelAsync: Este método solicita la cancelación de una operación en segundo plano pendiente.

CancellationPending: Esta propiedad indica que la aplicación ha solicitado la cancelación de una operación en segundo plano

DoWork: Este evento ocurre cuando el método RunWorkerAsync es llamado. El código en el evento DoWork es ejecutado en un hilo separado.

IsBusy: Esta propiedad indica que el componente BackgroundWorker esta actualmente ejecutando una operación asincrónica

ProgressChanged: Este evento ocurre cuando el método ReportProgress es llamado

ReportProgress: Este método invoca el evento ProgressChanged.

RunWorkerAsync: Este método inicia la ejecución de una operación en segundo plano invocando el evento DoWork.

RunWorkerCompleted: Este evento ocurre cuando la operación en segundo plano ha sido completada o cancelada, o a disparado una excepción.

WorkerReportProgress: Esta propiedad indica que el componente BackgroundWorker puede reportar actualizaciones de progreso.

WorkerSupportsCancellation: Este propiedad indica que el BackgroundWorker soporta cancelación asincrónica.

Referencias:

MSDN

– Libro: MCTS Self-Paced Training Kit (Exam 70-502): Microsoft® .NET Framework 3.5—Windows® Presentation Foundation

Codigo Fuente de la Aplicación Aquí.

10 pensamientos en “WPF – Procesos en segundo Plano (BackgroundWorker)

  1. Pingback: Twitter Trackbacks for WPF – Procesos en segundo Plano (BackgroundWorker) « Escarbando Código [escarbandocodigo.wordpress.com] on Topsy.com

  2. Pingback: uberVU - social comments

  3. Tiene muy buena pinta, lo voy a probar, había utilizado este componente, pero tenía un problema con los parámetros.
    Muchas gracias por esta aportación que haces

  4. Pingback: El Blog en números del 2010 « Escarbando Código

  5. Muchas gracias por el aporte….solo una cosita para que el progreso llegue a 100 completamente ahi que poner en el for <= 100 y listo.

    Saludos

      • hola MARTIN buenas tardes espero que me puedas ayudar tengo una subrutina con 8 parametros para enviar un correo electronico lo malo es que se demora aprox 8 segundos para mandarlo por eso queria ponerlo en segundo plano pero no se como poner esa subrutina con 8 parametros en segundo plano no encuentro nada en internet me podrias mandar un pequeño ejemplo como podria hacerñlo muchas gracias.

      • Hola, ya que en método RunWorkerAsync recibe un objeto, y ese es el parámetro, si quieres pasar múltiples parámetros, puedes crear una clase que tenga estás propiedades, algo así como:
        Class MisParametros
        public parametro1 as String
        Public parametro2 as Integer
        Public parametro3 as decimal
        EndClass
        Luego, los inicializas con los valores:
        Dim param as new MisParametros
        param.parametro1 = “param1”;
        param.parametro2 = 10;
        param.parametro3 = 1000.0;
        y al ejecutar el worker, envías el objeto:
        MyWorker.RunWorkerAsync(param)
        Y en el método bgw_DoWork, conviertes el e.Argument a la clase MisParametros y obtienes cada propiedad que serán los parámetros

Deseas comentar o sugerir algo?

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