C# - BackgroundWorker (Clase)

Descripción general

Ejecuta una operación en un subproceso independiente. La clase BackgroundWorker permite ejecutar una operación en un subproceso dedicado e independiente. Durante la ejecución de operaciones que exigen mucho tiempo, como las descargas y las transacciones de las bases de datos, puede parecer que la interfaz de usuario (UI) ha dejado de responder. Cuando se necesita una interfaz de usuario rápida, pero se producen largos retrasos asociados a tales operaciones, la clase BackgroundWorker ofrece una práctica solución.

[TOC] Tabla de Contenidos


↑↑↑

C# - BackgroundWorker (Clase)

Para ejecutar en segundo plano una operación que exija mucho tiempo, cree un objeto BackgroundWorker y realice escuchas de eventos que creen informes del progreso de la operación y que señalen su finalización.

Para preparar una operación en segundo plano,


↑↑↑

Ventajas

La principal ventaja de usar esta clase es que el proceso BackgroundWorker se realiza en el hilo donde está la pantalla, y por lo tanto, sus eventos están en el mismo hilo, y se puede acceder a los controles de la pantalla (por ejemplo Una barra de progreso –ProgressBar-) de forma trasparente sin tener que usar la clase Dispatcher.


↑↑↑

Ejemplo

Realizar en segundo plano una operación [Save] que está en el menú edición de un formulario

Primero, y aunque no es necesario, se diseña una clase que contendrá la operación y los parámetros que necesita la operación Save, es decir, el nombre del fichero y el texto que hay que guardar.


class HiloSaveFichero
{
    public string NombreCompletoArchivo { get; set; }
    public string TextoAgrabar { get; set; }
    public bool TareaTerminada { get; set; }
 
 
    public HiloSaveFichero()
    {
        NombreCompletoArchivo = string.Empty;
        TextoAgrabar = string.Empty;
        TareaTerminada = false;
    }
 
    public HiloSaveFichero(string nombreCompletoArchivo, string textoAgrabar)
        : this()
    {
        this.NombreCompletoArchivo = nombreCompletoArchivo;
        this.TextoAgrabar = textoAgrabar;
    }
 
 
 
    // Los hilos No tienen parámetros
    // Los hilos no devuelven nada
    public bool SaveFicheroConBackgroundWorker()
    {
        try
        {
            return SaveFichero();
        }
        catch (Exception ex)
        {
            TareaTerminada = false;
            // lanzar el error a la clase BackgroundWorker
            // para que atualice el evento [worker_RunWorkerCompleted]
            throw ex;
        }
    }
 
    private bool SaveFichero()
    {
        TareaTerminada = false;
        using (FileStream fs = new FileStream(NombreCompletoArchivo,
                                    FileMode.OpenOrCreate,
                                    FileAccess.Write,
                                    FileShare.None))
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                sw.Write(TextoAgrabar);
            }
        }
        TareaTerminada = true;
        return TareaTerminada;
    }
 
}

A continuación en el formulario escribimos el código necesario para usar esta clase


#region " Save con Hilos BackgroundWorker"

    public void StartOperacionAsincrona()
    {
        AccionBotonSaveBackgroundWorker();
     }
 
    public void CancelOperacionAsincrona()
    {
        if (worker.WorkerSupportsCancellation == true)
        {
            // Cancelar la operacion asincrona
            worker.CancelAsync();
        }
    }
 
 
 
    private void AccionBotonSaveBackgroundWorker()
    {
        if (worker == null)
        {
            //System.ComponentModel.BackgroundWorker worker = new BackgroundWorker();
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
 
            // Declarar los eventos de BackgroundWorker
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        }
 
      if (worker.IsBusy != true)
        {
         // poner en marcha la operacion en segundo plano
            // se va a disparar el evento [worker_DoWork] y se va a ejecutar su contenido
        // alli es donde hay que poner la opercion que debe realizarse
 
 
            objHiloSave = new HiloSaveFichero(
                             this.TextBoxNombreFichero.Text, 
                             this.TextBoxTextoCualquiera.Text);
 
            worker.RunWorkerAsync();
        }
    }
 
 
    // Aquí es donde se inicia el proceso
    // Aquí se tiene que llamar al proceso que ocupa mucho tiempo
    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
 
        //  Asignar el resultado del cálculo
        //  A la propiedad Result del objeto DoWorkEventArgs
        //  De esta forma estará disponible para el
        //  evento RunWorkerCompleted.
        e.Result = objHiloSave.SaveFicheroConBackgroundWorker();
 
    }
 
    // Este evento sirve para actualizar una barra de progreso (por ejemplo)
    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        throw new NotImplementedException();
        //this.progressBar1.Value = e.ProgressPercentage;
 
    }
 
    // Se dispara Cuando se termina el proceso en segundo plano
    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled == true)
        {
            AccionEscribeEnBarraEstado("¡Cancelado por el usuario!");
        }
        else
        {
            if (e.Error != null)
            {
                AccionEscribeEnBarraEstado("Error: " + e.Error.Message);
            }
            else
            {
                    AccionEscribeEnBarraEstado("Terminado - AccionBotonSaveBackgroundWorker");
            }
        }
    }
 
#endregion

Y la función que usaremos para escribir un texto en la barra de estado


private void AccionEscribeEnBarraEstado(string texto)
    {
        statusBarItemInformes.Content = texto;
    }


↑↑↑

La función Dispatcher

En este escenario, usando BackgroundWorker, no va a haber problemas de seguridad WPF porque un hilo intente usar un control de la pantalla, pero aun así (para no olvidarme) escribo la función que hace el mismo trabajo (escribir en la barra de estado) pero usando el método Dispatcher, es decir para ambientes multihilos.

Evidentemente, hay que escribir una función de este tipo por cada uno de los recursos de la pantalla que quiera usar un hilo (por ejemplo un control ProgressBar)


// Usar el metodo DispatcherObject.CheckAccess para determinar si 
// la llamada se realiza desde este proceso o desde otro hilo
private void AccionEscribeEnBarraEstado(string texto)
{
 
    if (this.statusBarItemInformes != null)
    {
        // Checking si este hilo tiene acceso al objeto
        if (this.statusBarItemInformes.CheckAccess())
        {
            // si el hilo tiene acceso al objeto
            statusBarItemInformes.Content = texto;
        }
        else
        {
            Action accioncita = new Action(() => statusBarItemInformes.Content = texto);
            this.Dispatcher.Invoke(accioncita, DispatcherPriority.ApplicationIdle);
        }
    }
}


↑↑↑

A.2.Enlaces

[Para saber mas]
[Grupo de documentos]
[Documento Index]
[Bloque de apuntes tácticos de C#]
[Documento Start]
[Imprimir el Documento]