Tutorial: Implementar un formulario que usa una operación en segundo plano

Si tiene una operación que va a tardar mucho tiempo en completarse y no desea que la interfaz de usuario (UI) deje de responder, puede utilizar la clase BackgroundWorker para ejecutar la operación en otro subproceso.

En este tutorial se muestra cómo utilizar la clase BackgroundWorker para realizar cálculos laboriosos "en segundo plano", mientras la interfaz de usuario sigue respondiendo. Cuando haya terminado, tendrá una aplicación que calcula de forma asincrónica los números de Fibonacci. Aunque se puede tardar un tiempo considerable en calcular un número de Fibonacci elevado, el subproceso de interfaz de usuario principal no se interrumpirá por este retraso, y el formulario seguirá respondiendo durante el cálculo.

Las tareas ilustradas en este tutorial incluyen:

  • Crear una aplicación basada en Windows

  • Crear un componente BackgroundWorker en el formulario

  • Agregar controladores de eventos asincrónicos

  • Agregar informes de progreso y compatibilidad con la cancelación

Para ver una lista completa del código utilizado en este ejemplo, consulte Cómo: Implementar un formulario que utiliza una operación en segundo plano.

Creación de un formulario que usa una operación en segundo plano

  1. En Visual Studio, cree un proyecto de aplicación basada en Windows llamado BackgroundWorkerExample (Archivo>Nuevo>Proyecto>Visual C# o Visual Basic>Escritorio clásico>Aplicación de Windows Forms).

  2. En el Explorador de soluciones, haga clic con el botón derecho en Form1 y seleccione Cambiar nombre en el menú contextual. Cambie el nombre del archivo a FibonacciCalculator. Haga clic en el botón cuando se le pregunte si desea cambiar el nombre de todas las referencias al elemento de código "Form1".

  3. Arrastre un control NumericUpDown del cuadro de herramientas al formulario. Establezca la propiedad Minimum en 1 y la propiedad Maximum en 91.

  4. Agregue dos controles Button al formulario.

  5. Cambie el nombre del primer control Button a startAsyncButton y establezca la propiedad Text en Start Async. Cambie el nombre del segundo control Button a cancelAsyncButton y establezca la propiedad Text en Cancel Async. Establezca su propiedad Enabled en false.

  6. Cree un controlador de eventos para los eventos Click de ambos controles Button. Para ver detalles, consulte Cómo: Crear controladores de eventos mediante el diseñador.

  7. Arrastre un control Label del cuadro de herramientas al formulario y cámbielo de nombre a resultLabel.

  8. Arrastre un control ProgressBar del cuadro de herramientas al formulario.

Creación de un componente BackgroundWorker con el Diseñador

Puede crear el componente BackgroundWorker para sus operaciones asincrónicas mediante el Diseñador de Forms de Windows.

Desde la pestaña Componentes del cuadro de herramientas, arrastre un componente BackgroundWorker al formulario.

Adición de controladores de eventos asincrónicos

Ya está listo para agregar controladores de eventos de los eventos asincrónicos del componente BackgroundWorker. Se llama a la laboriosa operación que se ejecutará en segundo plano, que calcula los números de Fibonacci, mediante uno de estos controladores de eventos.

  1. En la ventana Propiedades, con el componente BackgroundWorker aún seleccionado, haga clic en el botón Eventos. Haga doble clic en los eventos DoWork y RunWorkerCompleted para crear controladores de eventos. Para más información sobre cómo utilizar controladores de eventos, consulte Cómo: Crear controladores de eventos mediante el diseñador.

  2. Cree un método llamado ComputeFibonacci en el formulario. Este método realiza el trabajo en sí y se ejecutará en segundo plano. Este código muestra la implementación recursiva del algoritmo de Fibonacci, que es notablemente ineficaz y tarda exponencialmente más tiempo en completarse para los números más elevados. Se usa aquí a modo de ilustración, para mostrar una operación que puede suponer grandes retrasos en su aplicación.

    // This is the method that does the actual work. For this
    // example, it computes a Fibonacci number and
    // reports progress as it does its work.
    long ComputeFibonacci( int n, BackgroundWorker^ worker, DoWorkEventArgs ^ e )
    {
       // The parameter n must be >= 0 and <= 91.
       // Fib(n), with n > 91, overflows a long.
       if ( (n < 0) || (n > 91) )
       {
          throw gcnew ArgumentException( "value must be >= 0 and <= 91","n" );
       }
    
       long result = 0;
       
       // Abort the operation if the user has cancelled.
       // Note that a call to CancelAsync may have set 
       // CancellationPending to true just after the
       // last invocation of this method exits, so this 
       // code will not have the opportunity to set the 
       // DoWorkEventArgs.Cancel flag to true. This means
       // that RunWorkerCompletedEventArgs.Cancelled will
       // not be set to true in your RunWorkerCompleted
       // event handler. This is a race condition.
       if ( worker->CancellationPending )
       {
          e->Cancel = true;
       }
       else
       {
          if ( n < 2 )
          {
             result = 1;
          }
          else
          {
             result = ComputeFibonacci( n - 1, worker, e ) + ComputeFibonacci( n - 2, worker, e );
          }
    
          // Report progress as a percentage of the total task.
          int percentComplete = (int)((float)n / (float)numberToCompute * 100);
          if ( percentComplete > highestPercentageReached )
          {
             highestPercentageReached = percentComplete;
             worker->ReportProgress( percentComplete );
          }
       }
    
       return result;
    }
    
    // This is the method that does the actual work. For this
    // example, it computes a Fibonacci number and
    // reports progress as it does its work.
    long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e)
    {
        // The parameter n must be >= 0 and <= 91.
        // Fib(n), with n > 91, overflows a long.
        if ((n < 0) || (n > 91))
        {
            throw new ArgumentException(
                "value must be >= 0 and <= 91", "n");
        }
    
        long result = 0;
    
        // Abort the operation if the user has canceled.
        // Note that a call to CancelAsync may have set
        // CancellationPending to true just after the
        // last invocation of this method exits, so this
        // code will not have the opportunity to set the
        // DoWorkEventArgs.Cancel flag to true. This means
        // that RunWorkerCompletedEventArgs.Cancelled will
        // not be set to true in your RunWorkerCompleted
        // event handler. This is a race condition.
    
        if (worker.CancellationPending)
        {
            e.Cancel = true;
        }
        else
        {
            if (n < 2)
            {
                result = 1;
            }
            else
            {
                result = ComputeFibonacci(n - 1, worker, e) +
                         ComputeFibonacci(n - 2, worker, e);
            }
    
            // Report progress as a percentage of the total task.
            int percentComplete =
                (int)((float)n / (float)numberToCompute * 100);
            if (percentComplete > highestPercentageReached)
            {
                highestPercentageReached = percentComplete;
                worker.ReportProgress(percentComplete);
            }
        }
    
        return result;
    }
    
    ' This is the method that does the actual work. For this
    ' example, it computes a Fibonacci number and
    ' reports progress as it does its work.
    Function ComputeFibonacci( _
        ByVal n As Integer, _
        ByVal worker As BackgroundWorker, _
        ByVal e As DoWorkEventArgs) As Long
    
        ' The parameter n must be >= 0 and <= 91.
        ' Fib(n), with n > 91, overflows a long.
        If n < 0 OrElse n > 91 Then
            Throw New ArgumentException( _
                "value must be >= 0 and <= 91", "n")
        End If
    
        Dim result As Long = 0
    
        ' Abort the operation if the user has canceled.
        ' Note that a call to CancelAsync may have set 
        ' CancellationPending to true just after the
        ' last invocation of this method exits, so this 
        ' code will not have the opportunity to set the 
        ' DoWorkEventArgs.Cancel flag to true. This means
        ' that RunWorkerCompletedEventArgs.Cancelled will
        ' not be set to true in your RunWorkerCompleted
        ' event handler. This is a race condition.
        If worker.CancellationPending Then
            e.Cancel = True
        Else
            If n < 2 Then
                result = 1
            Else
                result = ComputeFibonacci(n - 1, worker, e) + _
                         ComputeFibonacci(n - 2, worker, e)
            End If
    
            ' Report progress as a percentage of the total task.
            Dim percentComplete As Integer = _
                CSng(n) / CSng(numberToCompute) * 100
            If percentComplete > highestPercentageReached Then
                highestPercentageReached = percentComplete
                worker.ReportProgress(percentComplete)
            End If
    
        End If
    
        Return result
    
    End Function
    
  3. En el controlador de eventos DoWork, agregue una llamada al método ComputeFibonacci. Tome el primer parámetro de ComputeFibonacci la propiedad Argument de DoWorkEventArgs. Los parámetros BackgroundWorker y DoWorkEventArgs se utilizarán más adelante en los informes de progreso y la compatibilidad con la cancelación. Asigne el valor devuelto de ComputeFibonacci a la propiedad Result de DoWorkEventArgs. Este resultado estará disponible para el controlador de eventos RunWorkerCompleted.

    Nota:

    El controlador de eventos DoWork no hace referencia a la variable de instancia backgroundWorker1 directamente, ya que esto uniría este controlador de eventos a una instancia específica de BackgroundWorker. En vez de ello, se recupera del parámetro sender una referencia al componente BackgroundWorker que generó este evento. Esto es importante cuando el formulario hospeda más de un componente BackgroundWorker. También es importante no manipular ningún objeto de la interfaz de usuario en el controlador de eventos DoWork. En su lugar, comuníquese con la interfaz de usuario a través de los eventos BackgroundWorker.

    // This event handler is where the actual,
    // potentially time-consuming work is done.
    void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e )
    {
       // Get the BackgroundWorker that raised this event.
       BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender);
    
       // Assign the result of the computation
       // to the Result property of the DoWorkEventArgs
       // object. This is will be available to the 
       // RunWorkerCompleted eventhandler.
       e->Result = ComputeFibonacci( safe_cast<Int32>(e->Argument), worker, e );
    }
    
    // This event handler is where the actual,
    // potentially time-consuming work is done.
    private void backgroundWorker1_DoWork(object sender,
        DoWorkEventArgs e)
    {
        // Get the BackgroundWorker that raised this event.
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // Assign the result of the computation
        // to the Result property of the DoWorkEventArgs
        // object. This is will be available to the
        // RunWorkerCompleted eventhandler.
        e.Result = ComputeFibonacci((int)e.Argument, worker, e);
    }
    
    ' This event handler is where the actual work is done.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
    
        ' Get the BackgroundWorker object that raised this event.
        Dim worker As BackgroundWorker = _
            CType(sender, BackgroundWorker)
    
        ' Assign the result of the computation
        ' to the Result property of the DoWorkEventArgs
        ' object. This is will be available to the 
        ' RunWorkerCompleted eventhandler.
        e.Result = ComputeFibonacci(e.Argument, worker, e)
    End Sub
    
  4. En el controlador de eventos Click del control startAsyncButton, agregue el código que inicia la operación asincrónica.

    void startAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ )
    {
       
       // Reset the text in the result label.
       resultLabel->Text = String::Empty;
    
       // Disable the UpDown control until 
       // the asynchronous operation is done.
       this->numericUpDown1->Enabled = false;
    
       // Disable the Start button until 
       // the asynchronous operation is done.
       this->startAsyncButton->Enabled = false;
    
       // Enable the Cancel button while 
       // the asynchronous operation runs.
       this->cancelAsyncButton->Enabled = true;
    
       // Get the value from the UpDown control.
       numberToCompute = (int)numericUpDown1->Value;
    
       // Reset the variable for percentage tracking.
       highestPercentageReached = 0;
    
       // Start the asynchronous operation.
       backgroundWorker1->RunWorkerAsync( numberToCompute );
    }
    
    private void startAsyncButton_Click(System.Object sender,
        System.EventArgs e)
    {
        // Reset the text in the result label.
        resultLabel.Text = String.Empty;
    
        // Disable the UpDown control until
        // the asynchronous operation is done.
        this.numericUpDown1.Enabled = false;
    
        // Disable the Start button until
        // the asynchronous operation is done.
        this.startAsyncButton.Enabled = false;
    
        // Enable the Cancel button while
        // the asynchronous operation runs.
        this.cancelAsyncButton.Enabled = true;
    
        // Get the value from the UpDown control.
        numberToCompute = (int)numericUpDown1.Value;
    
        // Reset the variable for percentage tracking.
        highestPercentageReached = 0;
    
        // Start the asynchronous operation.
        backgroundWorker1.RunWorkerAsync(numberToCompute);
    }
    
    Private Sub startAsyncButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles startAsyncButton.Click
    
        ' Reset the text in the result label.
        resultLabel.Text = [String].Empty
    
        ' Disable the UpDown control until 
        ' the asynchronous operation is done.
        Me.numericUpDown1.Enabled = False
    
        ' Disable the Start button until 
        ' the asynchronous operation is done.
        Me.startAsyncButton.Enabled = False
    
        ' Enable the Cancel button while 
        ' the asynchronous operation runs.
        Me.cancelAsyncButton.Enabled = True
    
        ' Get the value from the UpDown control.
        numberToCompute = CInt(numericUpDown1.Value)
    
        ' Reset the variable for percentage tracking.
        highestPercentageReached = 0
    
    
        ' Start the asynchronous operation.
        backgroundWorker1.RunWorkerAsync(numberToCompute)
    End Sub 
    
  5. En el controlador de eventos RunWorkerCompleted, asigne el resultado del cálculo al control resultLabel.

    // This event handler deals with the results of the
    // background operation.
    void backgroundWorker1_RunWorkerCompleted( Object^ /*sender*/, RunWorkerCompletedEventArgs^ e )
    {
       // First, handle the case where an exception was thrown.
       if ( e->Error != nullptr )
       {
          MessageBox::Show( e->Error->Message );
       }
       else
       if ( e->Cancelled )
       {
          // Next, handle the case where the user cancelled 
          // the operation.
          // Note that due to a race condition in 
          // the DoWork event handler, the Cancelled
          // flag may not have been set, even though
          // CancelAsync was called.
          resultLabel->Text = "Cancelled";
       }
       else
       {
          // Finally, handle the case where the operation 
          // succeeded.
          resultLabel->Text = e->Result->ToString();
       }
    
       // Enable the UpDown control.
       this->numericUpDown1->Enabled = true;
    
       // Enable the Start button.
       startAsyncButton->Enabled = true;
    
       // Disable the Cancel button.
       cancelAsyncButton->Enabled = false;
    }
    
    // This event handler deals with the results of the
    // background operation.
    private void backgroundWorker1_RunWorkerCompleted(
        object sender, RunWorkerCompletedEventArgs e)
    {
        // First, handle the case where an exception was thrown.
        if (e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }
        else if (e.Cancelled)
        {
            // Next, handle the case where the user canceled
            // the operation.
            // Note that due to a race condition in
            // the DoWork event handler, the Cancelled
            // flag may not have been set, even though
            // CancelAsync was called.
            resultLabel.Text = "Canceled";
        }
        else
        {
            // Finally, handle the case where the operation
            // succeeded.
            resultLabel.Text = e.Result.ToString();
        }
    
        // Enable the UpDown control.
        this.numericUpDown1.Enabled = true;
    
        // Enable the Start button.
        startAsyncButton.Enabled = true;
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    ' This event handler deals with the results of the
    ' background operation.
    Private Sub backgroundWorker1_RunWorkerCompleted( _
    ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
    Handles backgroundWorker1.RunWorkerCompleted
    
        ' First, handle the case where an exception was thrown.
        If (e.Error IsNot Nothing) Then
            MessageBox.Show(e.Error.Message)
        ElseIf e.Cancelled Then
            ' Next, handle the case where the user canceled the 
            ' operation.
            ' Note that due to a race condition in 
            ' the DoWork event handler, the Cancelled
            ' flag may not have been set, even though
            ' CancelAsync was called.
            resultLabel.Text = "Canceled"
        Else
            ' Finally, handle the case where the operation succeeded.
            resultLabel.Text = e.Result.ToString()
        End If
    
        ' Enable the UpDown control.
        Me.numericUpDown1.Enabled = True
    
        ' Enable the Start button.
        startAsyncButton.Enabled = True
    
        ' Disable the Cancel button.
        cancelAsyncButton.Enabled = False
    End Sub
    

Agregar informes de progreso y compatibilidad con la cancelación

Para las operaciones asincrónicas que vayan a tardar mucho tiempo, suele ser deseable informar sobre el progreso al usuario y permitir que cancele la operación. La clase BackgroundWorker proporciona un evento que permite publicar el progreso a medida que la operación en segundo plano se realice. También proporciona una marca que permite que el código de trabajo detecte una llamada a CancelAsync y se interrumpa.

Implementación de informes de progreso

  1. En la ventana Propiedades, seleccione backgroundWorker1. Establezca las propiedades WorkerReportsProgress y WorkerSupportsCancellation en true.

  2. Declare dos variables en el formulario FibonacciCalculator. Se utilizarán para realizar un seguimiento del progreso.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Agregue un controlador de eventos para el evento ProgressChanged. En el controlador de eventos ProgressChanged, actualice ProgressBar con la propiedad ProgressPercentage del parámetro ProgressChangedEventArgs.

    // This event handler updates the progress bar.
    void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e )
    {
       this->progressBar1->Value = e->ProgressPercentage;
    }
    
    // This event handler updates the progress bar.
    private void backgroundWorker1_ProgressChanged(object sender,
        ProgressChangedEventArgs e)
    {
        this.progressBar1.Value = e.ProgressPercentage;
    }
    
    ' This event handler updates the progress bar.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
    
        Me.progressBar1.Value = e.ProgressPercentage
    
    End Sub
    

Implementación de la compatibilidad con la cancelación

  1. En el controlador de eventos Click del control cancelAsyncButton, agregue el código que cancela la operación asincrónica.

    void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ )
    {  
       // Cancel the asynchronous operation.
       this->backgroundWorker1->CancelAsync();
       
       // Disable the Cancel button.
       cancelAsyncButton->Enabled = false;
    }
    
    private void cancelAsyncButton_Click(System.Object sender,
        System.EventArgs e)
    {
        // Cancel the asynchronous operation.
        this.backgroundWorker1.CancelAsync();
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    Private Sub cancelAsyncButton_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles cancelAsyncButton.Click
        
        ' Cancel the asynchronous operation.
        Me.backgroundWorker1.CancelAsync()
    
        ' Disable the Cancel button.
        cancelAsyncButton.Enabled = False
        
    End Sub
    
  2. Los siguientes fragmentos de código en el método ComputeFibonacci informan sobre el progreso y son compatibles con la cancelación.

    if ( worker->CancellationPending )
    {
       e->Cancel = true;
    }
    
    if (worker.CancellationPending)
    {
        e.Cancel = true;
    }
    
    If worker.CancellationPending Then
        e.Cancel = True
    
    // Report progress as a percentage of the total task.
    int percentComplete = (int)((float)n / (float)numberToCompute * 100);
    if ( percentComplete > highestPercentageReached )
    {
       highestPercentageReached = percentComplete;
       worker->ReportProgress( percentComplete );
    }
    
    // Report progress as a percentage of the total task.
    int percentComplete =
        (int)((float)n / (float)numberToCompute * 100);
    if (percentComplete > highestPercentageReached)
    {
        highestPercentageReached = percentComplete;
        worker.ReportProgress(percentComplete);
    }
    
    ' Report progress as a percentage of the total task.
    Dim percentComplete As Integer = _
        CSng(n) / CSng(numberToCompute) * 100
    If percentComplete > highestPercentageReached Then
        highestPercentageReached = percentComplete
        worker.ReportProgress(percentComplete)
    End If
    

Punto de control

En este punto, puede compilar y ejecutar la aplicación de calculadora de Fibonacci.

Presione F5 para compilar y ejecutar la aplicación.

Mientras se ejecuta el cálculo en segundo plano, verá que ProgressBar muestra el progreso del cálculo hacia su finalización. También puede cancelar la operación pendiente.

Para los números pequeños, el cálculo debería ser muy rápido pero, para los números mayores, debería darse un retraso notable. Si escribe un valor de 30 o superior, debería ver un retraso de varios segundos, dependiendo de la velocidad del equipo. Para los valores superiores a 40, se pueden tardar minutos u horas en finalizar el cálculo. Mientras la calculadora está ocupada calculando un número de Fibonacci elevado, observe que tiene libertad para mover el formulario, minimizarlo, maximizarlo e incluso descartarlo. Esto se debe a que el subproceso de interfaz de usuario principal no está esperando a que finalice el cálculo.

Pasos siguientes

Ahora que ha implementado un formulario que utiliza un componente BackgroundWorker para ejecutar un cálculo en segundo plano, puede explorar otras posibilidades de las operaciones asincrónicas:

Consulte también