Udostępnij za pośrednictwem


Walkthrough: Implementing a Form That Uses a Background Operation

If you have an operation that will take a long time to complete, and you do not want your user interface (UI) to stop responding or "hang," you can use the BackgroundWorker class to execute the operation on another thread.

This walkthrough illustrates how to use the BackgroundWorker class to perform time-consuming computations "in the background," while the user interface remains responsive. When you are through, you will have an application that computes Fibonacci numbers asynchronously. Even though computing a large Fibonacci number can take a noticeable amount of time, the main UI thread will not be interrupted by this delay, and the form will be responsive during the calculation.

Tasks illustrated in this walkthrough include:

  • Creating a Windows-based Application

  • Creating a BackgroundWorker in Your Form

  • Adding Asynchronous Event Handlers

  • Adding Progress Reporting and Support for Cancellation

For a complete listing of the code used in this example, see How to: Implement a Form That Uses a Background Operation.

Note

The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Visual Studio Settings.

Creating the Project

The first step is to create the project and to set up the form.

To create a form that uses a background operation

  1. Create a Windows-based application project called BackgroundWorkerExample. For details, see How to: Create a Windows Application Project.

  2. In Solution Explorer, right-click Form1 and select Rename from the shortcut menu. Change the file name to FibonacciCalculator. Click the Yes button when you are asked if you want to rename all references to the code element 'Form1'.

  3. Drag a NumericUpDown control from the Toolbox onto the form. Set the Minimum property to 1 and the Maximum property to 91.

  4. Add two Button controls to the form.

  5. Rename the first Button control startAsyncButton and set the Text property to Start Async. Rename the second Button control cancelAsyncButton, and set the Text property to Cancel Async. Set its Enabled property to false.

  6. Create an event handler for both of the Button controls' Click events. For details, see How to: Create Event Handlers Using the Designer.

  7. Drag a Label control from the Toolbox onto the form and rename it resultLabel.

  8. Drag a ProgressBar control from the Toolbox onto the form.

Creating a BackgroundWorker in Your Form

You can create the BackgroundWorker for your asynchronous operation using the WindowsForms Designer.

To create a BackgroundWorker with the Designer

  • From the Components tab of the Toolbox, drag a BackgroundWorker onto the form.

Adding Asynchronous Event Handlers

You are now ready to add event handlers for the BackgroundWorker component's asynchronous events. The time-consuming operation that will run in the background, which computes Fibonacci numbers, is called by one of these event handlers.

To implement asynchronous event handlers

  1. In the Properties window, with the BackgroundWorker component still selected, click the Events button. Double-click the DoWork and RunWorkerCompleted events to create event handlers. For more information about how to use event handlers, see How to: Create Event Handlers Using the Designer.

  2. Create a new method, called ComputeFibonacci, in your form. This method does the actual work, and it will run in the background. This code demonstrates the recursive implementation of the Fibonacci algorithm, which is notably inefficient, taking exponentially longer time to complete for larger numbers. It is used here for illustrative purposes, to show an operation that can introduce long delays in your application.

    ' 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. In the DoWork event handler, add a call to the ComputeFibonacci method. Take the first parameter for ComputeFibonacci from the Argument property of the DoWorkEventArgs. The BackgroundWorker and DoWorkEventArgs parameters will be used later for progress reporting and cancellation support. Assign the return value from ComputeFibonacci to the Result property of the DoWorkEventArgs. This result will be available to the RunWorkerCompleted event handler.

    Note

    The DoWork event handler does not reference the backgroundWorker1 instance variable directly, as this would couple this event handler to a specific instance of BackgroundWorker. Instead, a reference to the BackgroundWorker that raised this event is recovered from the sender parameter. This is important when the form hosts more than one BackgroundWorker. It is also important not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the BackgroundWorker events.

    ' 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. In the startAsyncButton control's Click event handler, add the code that starts the asynchronous operation.

    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. In the RunWorkerCompleted event handler, assign the result of the calculation to the resultLabel control.

    ' 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;
    }
    

Adding Progress Reporting and Support for Cancellation

For asynchronous operations that will take a long time, it is often desirable to report progress to the user and to allow the user to cancel the operation. The BackgroundWorker class provides an event that allows you to post progress as your background operation proceeds. It also provides a flag that allows your worker code to detect a call to CancelAsync and interrupt itself.

To implement progress reporting

  1. In the Properties, window, select backgroundWorker1. Set the WorkerReportsProgress and WorkerSupportsCancellation properties to true.

  2. Declare two variables in the FibonacciCalculator form. These will be used to track progress.

    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    int numberToCompute;
    int highestPercentageReached;
    
  3. Add an event handler for the ProgressChanged event. In the ProgressChanged event handler, update the ProgressBar with the ProgressPercentage property of the ProgressChangedEventArgs parameter.

    ' 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;
    }
    

To implement support for cancellation

  1. In the cancelAsyncButton control's Click event handler, add the code that cancels the asynchronous operation.

    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. The following code fragments in the ComputeFibonacci method report progress and support cancellation.

    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 );
    }
    

Checkpoint

At this point, you can compile and run the Fibonacci Calculator application.

To test your project

  • Press F5 to compile and run the application.

    While the calculation is running in the background, you will see the ProgressBar displaying the progress of the calculation toward completion. You can also cancel the pending operation.

    For small numbers, the calculation should be very fast, but for larger numbers, you should see a noticeable delay. If you enter a value of 30 or greater, you should see a delay of several seconds, depending on the speed of your computer. For values greater than 40, it may take minutes or hours to finish the calculation. While the calculator is busy computing a large Fibonacci number, notice that you can freely move the form around, minimize, maximize, and even dismiss it. This is because the main UI thread is not waiting for the calculation to finish.

Next Steps

Now that you have implemented a form that uses a BackgroundWorker component to execute a computation in the background, you can explore other possibilities for asynchronous operations:

See Also

Tasks

How to: Implement a Form That Uses a Background Operation

Walkthrough: Running an Operation in the Background

Concepts

Managed Threading Best Practices

Reference

BackgroundWorker

Other Resources

Multithreading in Components

Multithreading in Visual Basic

BackgroundWorker Component

Change History

Date

History

Reason

April 2009

Corrected a bug that was preventing some steps from being displayed.

Content bug fix.