Share via


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

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

Este tutorial muestra cómo utilizar la clase BackgroundWorker para realizar operaciones "en segundo plano" que tardan mucho tiempo, mientras la interfaz de usuario permanece receptiva. Cuando haya terminado, tendrá una aplicación que calcula de forma asincrónica series de Fibonacci. Aunque el cálculo de series de Fibonacci muy grandes puede tardar una considerable cantidad de tiempo, el subproceso principal de la interfaz de usuario no se interrumpirá por este retraso, y el formulario seguirá receptivo durante la operación.

Las tareas ilustradas en este tutorial incluyen:

  • Crear una aplicación basada en Windows

  • Crear un BackgroundWorker en el formulario

  • Agregar los controladores de eventos asincrónicos

  • Agregar la información del progreso y la admisión de la cancelación

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

Nota

Los cuadros de diálogo y comandos de menú que se ven pueden diferir de los descritos en la Ayuda, en función de los valores de configuración o de edición activos. Para cambiar la configuración, elija la opción Importar y exportar configuraciones del menú Herramientas. Para obtener más información, vea Trabajar con valores de configuración.

Crear el proyecto

El primer paso es crear el proyecto y configurar el formulario.

Para crear un formulario que utiliza una operación en segundo plano

  1. Cree un proyecto de aplicación basada en Windows denominado BackgroundWorkerExample. Para obtener información detallada, vea Cómo: Crear un nuevo proyecto de aplicación de Windows Forms.

  2. En el Explorador de soluciones, haga clic con el botón secundario del mouse en Form1 y elija Cambiar nombre en el menú contextual. Cambie el nombre de archivo 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 NumericUpDown desde el 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 startAsyncButton del control Button y establezca la propiedad Text en Start Async. Cambie el nombre del segundo cancelAsyncButton del control Button y establezca la propiedad Text en Cancel Async. Establezca la propiedad Enabled en false.

  6. Cree un controlador de eventos para ambos eventos Click de los controles Button. Para obtener información detallada, vea Cómo: Crear controladores de eventos con el diseñador.

  7. Arrastre un Label desde el Cuadro de herramientas al formulario y cambie el nombre a resultLabel.

  8. Arrastre un ProgressBar desde el Cuadro de herramientas al formulario.

Crear un control BackgroundWorker en el formulario

Puede crear un BackgroundWorker para la operación asincrónica mediante el Diseñador de Windows Forms.

Para crear un control BackgroundWorker con el diseñador

  • En la ficha Componentes del Cuadro de herramientas, arrastre un componente BackgroundWorker al formulario.

Agregar los controladores de eventos asincrónicos

Ahora ya se pueden agregar los controladores de eventos para los eventos asincrónicos del componente BackgroundWorker. Uno de estos controladores de eventos llama a la operación que lleva mucho tiempo y que se ejecutará en segundo plano, que calcula la serie de Fibonacci.

Para implementar los controladores de eventos asincrónicos

  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 los controladores de eventos. Para obtener más información acerca de cómo utilizar controladores de eventos, vea Cómo: Crear controladores de eventos con el diseñador.

  2. Cree un nuevo método, denominado ComputeFibonacci, en el formulario. Este método hace el trabajo real y se ejecutará en segundo plano. Este código muestra la implementación recursiva del algoritmo de Fibonacci, notablemente ineficaz, que lleva exponencialmente mucho más tiempo para finalizar los números más grandes. Se utiliza aquí con fines ilustrativos, para mostrar una operación que puede introducir grandes retrasos en la 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.
    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
    
    // 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.
    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;
    }
    
  3. En el controlador de eventos DoWork, agregue una llamada al método ComputeFibonacci. Tome el primer parámetro para ComputeFibonacci desde la propiedad Argument de DoWorkEventArgs. Los parámetros BackgroundWorker y DoWorkEventArgs se utilizarán después para la información del progreso y para admitir la cancelación. Asigne el valor devuelto de ComputeFibonacci a la propiedad Result del objeto DoWorkEventArgs. Este resultado estará disponible para el controlador de eventos RunWorkerCompleted.

    Nota

    El controlador de eventos DoWork no hace referencia directamente a la variable de la instancia backgroundWorker1, ya que de este modo se uniría este controlador de eventos a una instancia específica de BackgroundWorker. En su lugar, se recupera del parámetro sender una referencia al control BackgroundWorker que provocó este evento. Esto es importante cuando el formulario hospede más de un BackgroundWorker. Es también 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 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 'backgroundWorker1_DoWork
    
    // 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,
    // 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 );
    }
    
  4. En el controlador de eventos Click del control startAsyncButton, agregue el código que inicia la operación asincrónica.

    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 
    
    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);
    }
    
    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 );
    }
    
  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.
    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 'backgroundWorker1_RunWorkerCompleted
    
    // 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.
    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;
    }
    

Agregar la información del progreso y la admisión de la cancelación

Para aquellas operaciones asincrónicas que tardan mucho tiempo, suele ser deseable informar del progreso al usuario y permitir que éste cancele la operación. La clase BackgroundWorker proporciona un evento que le permite enviar el progreso a medida que se realiza la operación en segundo plano. También proporciona un marcador que permite a su código de trabajo detectar una llamada a CancelAsync e interrumpirse.

Para implementar la información de progreso

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

  2. Declare dos variables en el formulario FibonacciCalculator. Éstas se utilizarán para realizar el seguimiento del progreso.

    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    int numberToCompute;
    int highestPercentageReached;
    
  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.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
    
        Me.progressBar1.Value = e.ProgressPercentage
    
    End Sub
    
    // 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.
    void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e )
    {
       this->progressBar1->Value = e->ProgressPercentage;
    }
    

Para implementar la admisión de la cancelación

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

    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 'cancelAsyncButton_Click
    
    private void cancelAsyncButton_Click(System.Object sender, 
        System.EventArgs e)
    {   
        // Cancel the asynchronous operation.
        this.backgroundWorker1.CancelAsync();
    
        // Disable the Cancel button.
        cancelAsyncButton.Enabled = false;
    }
    
    void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ )
    {  
       // Cancel the asynchronous operation.
       this->backgroundWorker1->CancelAsync();
    
       // Disable the Cancel button.
       cancelAsyncButton->Enabled = false;
    }
    
  2. Los fragmentos de código siguientes del método ComputeFibonacci informan del progreso y admiten la cancelación.

    If worker.CancellationPending Then
        e.Cancel = True
    
    
    ...
    
    
    ' 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
    
    if (worker.CancellationPending)
    {   
        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);
    }
    
    if ( worker->CancellationPending )
    {
       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 );
    }
    

Punto de control

Llegado este punto, puede generar y ejecutar la aplicación Fibonacci Calculator.

Para probar el proyecto

  • Presione F5 para compilar y ejecutar la aplicación.

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

    En el caso de números pequeños, el cálculo debería ser muy rápido, pero con los números más grandes, debería ver un considerable retraso. Si ha especificado un valor de 30 o superior, debería ver un retraso de varios segundos, dependiendo de la velocidad de su equipo. Para los valores mayores de 40, puede tardar minutos u horas finalizar el cálculo. Mientras la calculadora está ocupada calculando una serie de Fibonacci grande, observe que se puede mover libremente por todo el formulario, minimizarlo, maximizarlo e incluso descartarlo. Esto es debido a que el subproceso principal de la interfaz de usuario 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 para las operaciones asincrónicas:

Vea también

Tareas

Cómo: Implementar un formulario que utiliza una operación en segundo plano

Tutorial: Ejecutar una operación en segundo plano

Referencia

BackgroundWorker

Conceptos

Procedimientos recomendados para el subprocesamiento administrado

Otros recursos

Subprocesamiento múltiple en componentes

Multithreading in Visual Basic

BackgroundWorker (Componente)