Pages

Friday, January 21, 2011

Using BackgroundWorker Component

I'll probably start every next post with this...

In my own words:
The BackgroundWorker component is an easy way of using threading. It gives you some events so that you can easily control your execution flow (progress, cancellation, etc). And, as who knows a little bit of threading already knows, it manages to keep your UI responsive while performing lengthy and time consuming operations on the background. It is somewhat of a wrapper, so you can use threading and report the progress withouth even putting much thoughts on it...

In MSDN's words:
The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, the BackgroundWorker class provides a convenient solution.


I'll use a simple form with a progress bar, two buttons and a BackgroundWorker, so the form will look like this:




Working the Worker

For this example, it's important to have the Asynchronous properties set to true, like the image below:


The properties' names are self-explanatory.

As for events, we have three of them:

  • DoWork:
    This is the main execution entry point. This is where you should put your asynchronous code, or your asynchronous entry method call.


  • ProgressChanged:
    This event does the progress reporting. If you want to report your progress on a progress bar, for example, this is where you should set its progress value.


  • RunWorkerCompleted:
    Self explanatory. Execution falls here right after work is completed, so that you can do any clean up, display any messages, etc.


  • Stating that, generate the event handlers:


    Properties:

    Code:


    To start running our example, we'll call method RunWorkerAsync inside Button Start's Click Event Handler:

    private void btnStart_Click(object sender, EventArgs e)
    {
        if (!backWorker.IsBusy)
        {
            backWorker.RunWorkerAsync();
        }
    }
    

    If you don't plan on disabling your start button or preventing the user from starting the operation again, you should put the check above. Property IsBusy returns us a boolean indicating if the BackgroundWorker is currently performing any background operation. If it is, and we call RunWorkerAsync again, it'll throw us an Exception of type InvalidOperationException with message "This BackgroundWorker is currently busy and cannot run multiple tasks concurrently.".

    Calling method RunWorkerAsync will execute whatever's inside the DoWork Event Handler, asynchronously. At this point, execution will already be in another thread.

    So let's put some dummy loop inside it just so it keeps looping for a while:

    private void backWorker_DoWork(object sender, DoWorkEventArgs e)
    {    
        for (int i = 1; i <= 100; i++)    
        {
            System.Threading.Thread.Sleep(500);
        }
    }
    

    Now assume that you want to report execution progress on that. The BackgroundWorker component gives us the method ReportProgress. We'll use the simple overload, which takes an integer value from 0 to 100 (0% to 100%), and raises the ProgressChanged event. Our code here will look like this:

    private void backWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 1; i <= 100; i++)
        {
            System.Threading.Thread.Sleep(500);
            backWorker.ReportProgress(i); // Don't forget that i represents a percentage from 0 to 100.
        }
    }
    

    And inside the Event Handler ProgressChanged, we'll set the progress bar's value to the value that we passed as a parameter when we called the method ReportProgress:

    private void backWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar.Value = e.ProgressPercentage; // Percentage value comes on ProgressChangedEventArgs
    }
    

    If we run our project here, we'll see the progress of execution on the progress bar. But what if we want to cancel it?BackgroundWorker has a readonly property named CancellationPending, and a method named CancelAsync. As you could already figure out, when we want to cancel an asynchronous operation, we call method CancelAsync and it'll set the CancellationPending property to true. We can then check if it's true everytime we reach a point in our code where we can actually cancel the operation.Call CancelAsync inside Button Cancel's Click Event Handler:

    private void btnCancel_Click(object sender, EventArgs e)
    {
        backWorker.CancelAsync();
    }
    

    Check the property CancellationPending on every iteration on our loop:

    private void backWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 1; i <= 100; i++)
        {
            if (backWorker.CancellationPending)
            {
                e.Cancel = true; // Here we're letting the Worker know that we're cancelling our operation
                return;
            }
            else
            {
                System.Threading.Thread.Sleep(500);
                backWorker.ReportProgress(i);
            }
        }
    }
    

    Notice that we set a property on DoEventArgs to true. We do that to indicate that the operation has been canceled when execution reaches our next event handler, RunWorkerCompleted. As we call return, execution will leave DoWork and fall into RunWorkerCompleted, where we can display any messages, or do some cleanup:

    private void backWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled) // Remember that we've indicated cancellation on DoWork?
            MessageBox.Show("User cancelled execution");
        else
            MessageBox.Show("Execution completed successfully");
    }
    

    So that's basically it. This is a simple way of using BackgroundWorker, just to show you how powerful it is. It literally simplifies using threading. Hope you like it.

    No comments:

    Post a Comment