The .NET BackgroundWorker class and You

By | May 30, 2015

Introduction

My personal favorite class of the .NET Framework is the BackgroundWorker class. The BackgroundWorker class provides an easy way for a programmer to execute a time-consuming operation on a separate background thread while providing a simple way to communicate progress, completion and other information about the background operation to the thread which started the background worker (typically this is the UI thread).

The most important aspect of the BackgroundWorker class, in my opinion, is it’s events. The three events which we will concern ourselves with are:

  1. DoWork – This event fires when the RunWorkerAsync method is called. What occurs in the DoWork event handler happens on the background thread. Do all of your time-consuming work in this event handler.
  2. ProgressChanged – This event fires whenever the ReportProgress method is called. For WPF and WinForm applications which start a background worker, then what occurs in the ProgressChanged event handler happens on the UI thread, that is, if the RunWorkerAsync method was invoked on the UI thread. Manage any progress update notifications in this event handler.
  3. RunWorkerCompleted – This event fires when the background operation is finished. For WPF and WinForm applications, what occurs in the RunWorkerCompleted event handler happens on the UI thread, again, if the RunWorkerAsync method was invoked on the UI thread. Use this event handler for any concluding activity once the background task has been completed – such as a notification to the user or to indicate success, cancellation, or failure of the background task.

Repeat after me: Never ever alter your UI controls in your DoWork event handler. Only use the ProgressChanged and RunWorkerCompleted event handlers to alter your UI controls within the context of the background worker. If you do not heed this warning you will get an InvalidOperationException stating:

Cross-thread operation not valid: Control ‘[Your Control Name Here]’ accessed from a thread other than the thread it was created on.

Something noteworthy about the DoWork event handler is that it eats up exceptions and spits them out in the RunWorkerCompleted event handler by placing them inside the Error property of the RunWorkerCompletedEventArgs object. It is important to check both the Cancelled and Error property of the RunWorkerCompletedEventArgs instance of the RunWorkerCompleted event handler before accessing the Result property, otherwise an exception will be raised.

Two other properties of the background worker worth noting are WorkerReportsProgress and WorkerSupportsCancellation. With WorkerReportsProgress set to true your code can call the ReportProgress method to raise the ProgressChanged event. Beware that if the WorkerReportsProgress property is set to false, then a call to ReportProgress will result in an InvalidOperationException being thrown. With WorkerSupportsCancellation set to true you can asynchronously cancel the background operation by calling the CancelAsync method. If the WorkerSupportsCancellation property is set to false, then a call to CancelAsync will result in an InvalidOperationException being thrown.

Example Application

So let’s see the BackgroundWorker class in action:

I personally prefer to set up the background worker in a separate initialization method and call it in the form constructor like so:

public partial class MainForm : Form
{
    private BackgroundWorker _backgroundWorker;
    private bool _closeRequested = false;

    public MainForm()
    {
        InitializeComponent();
        InitializeBackgroundWorker();
    }

    private void InitializeBackgroundWorker()
    {
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.WorkerReportsProgress = true;
        _backgroundWorker.WorkerSupportsCancellation = true;
        _backgroundWorker.DoWork += 
             new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.ProgressChanged += 
            new ProgressChangedEventHandler(
                           backgroundWorker_ProgressChanged);
        _backgroundWorker.RunWorkerCompleted += 
            new RunWorkerCompletedEventHandler(
                           backgroundWorker_RunWorkerCompleted);
    }
    
    ...
}

When the user clicks the Start button, then the background worker operation is kicked off by calling RunWorkerAsync. In the same click event handler we also set up the state of the UI.

private void startButton_Click(object sender, EventArgs e)
{
    int numberOfPosIntsToCheck = (int)numericUpDown.Value;
    if (_backgroundWorker.IsBusy == false)
    {
         startButton.Enabled = false;
         cancelButton.Enabled = true;
         numericUpDown.Enabled = false;
         resultsTextBox.Text = string.Empty;
         resultsTextBox.BackColor = SystemColors.Window;
         _backgroundWorker.RunWorkerAsync(numberOfPosIntsToCheck);
    }
}

Soon after the RunWorkerAsync method is called, the DoWork event is raised which begins execution of the background operation. Here we loop through each positive integer in the specified range and test it for primality. We report the progress percentage and if the number is prime we notify the user of it’s discovery. We also keep track of how many prime numbers we found in total.

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    int numOfPositiveIntegersToCheck = (int)e.Argument;
    int primeCount = 0;

    for (int i = 1; i <= numOfPositiveIntegersToCheck; i++)
    {
        if (worker.CancellationPending == true)
        {
            e.Cancel = true;
            break;
        }
                              
        int percentComplete = (int)Math.Round((double)(100 * i) / numOfPositiveIntegersToCheck);
        if (IsNumberPrime(i))
        {
            worker.ReportProgress(percentComplete, string.Format("Found a Prime Number: {0}", i));
            primeCount++;
        }
        else
        {
            worker.ReportProgress(percentComplete);
        }

        // Wait a tick...
        // Otherwise messages get pumped to the UI thread so fast that the UI becomes unresponsive
        System.Threading.Thread.Sleep(1);
    }

    e.Result = primeCount;
}

Whenever the ReportProgress method is called the ProgressChanged event is raised. This is where we update the ProgressBar control and the accompanying Label control. If we found a prime number, then the UserState property of the ProgressChangedEventArgs object will contain the message for us to update the TextBox control with.

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar.Value = e.ProgressPercentage;
    progressLabel.Text = string.Format("Progress: {0}%", e.ProgressPercentage);
    progressLabel.Refresh();

    if (e.UserState != null && e.UserState is string)
    {
         resultsTextBox.AppendText((string)e.UserState + Environment.NewLine);
    }
}

Note that our background worker supports cancellation. So if the user decides that they want to scan through the first million positive integers for primes, but later decide that this is not what they wished to do, then the user has the ability to request that the background worker cancel the operation. The operation can be cancelled by simply clicking the Cancel button which invokes the CancelAsync method which in turn sets the CancellationPending property to true. If you recall, in the DoWork event handler we check the CancellationPending property of the background worker at the start of each iteration of the for loop. If the user has indicated that they wish to cancel the operation, then we break out of our loop and signal that the operation has been cancelled (this last part is important to our RunWorkerCompleted event handler).

private void cancelButton_Click(object sender, EventArgs e)
{
    if (_backgroundWorker.WorkerSupportsCancellation)
    {
        // Sets the CancellationPending property to true
        _backgroundWorker.CancelAsync();
    }
}

Lastly, the RunWorkerCompleted event handler ties a little bow onto our background operation. In this case, if the operation was cancelled then we print to the TextBox control that the operation was cancelled and turn the background red. In the case of an error, we print the error to the TextBox control and turn the background orange. If the operation completed normally then we print to the TextBox control how many prime numbers were found and turn the background green. I also take the opportunity to reset the state of some of the UI controls.

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        resultsTextBox.AppendText(string.Format("Error: {0}", e.Error.Message) + Environment.NewLine);
        resultsTextBox.BackColor = Color.Orange;
    }
    else if (e.Cancelled)
    {
        resultsTextBox.AppendText("Operation was cancelled!" + Environment.NewLine);
        resultsTextBox.BackColor = Color.IndianRed;
    }
    else
    {
        resultsTextBox.AppendText(string.Format("Found {0} Primes!", (int)e.Result) + Environment.NewLine);
        resultsTextBox.BackColor = Color.LawnGreen;
    }

    if (_closeRequested)
    {
        Close();
    }
    else
    {
        startButton.Enabled = true;
        cancelButton.Enabled = false;
        numericUpDown.Enabled = true;
    }
}

And that’s it! That’s all you need in order to get a working example of using the background worker.

A Deeper Look

So how does this all work in a standard WinForm or WPF application? First, it is worth noting that things get hairier in console applications and Windows Services – I will briefly explain this a bit later. When the RunWorkerAsync method is called, the background worker captures the SynchronizationContext of the thread that calls this method. This is done by calling the AsyncOperationManager.CreateOperation method to obtain an instance of the AsyncOperation class. So when the UI thread starts the background worker, the background worker captures the UI synchronization context (WindowsFormsSynchronizationContext in a WinForm application and DispatcherSynchronizationContext in a WPF application). The DoWork handler is executed by a ThreadPool thread which uses the default synchronization context. Progress and Completion notifications are marshaled to the UI thread via a call to the Post and PostOperationCompleted methods of the AsyncOperation object. Using these methods, the AsyncOperation object will ensure that the supplied delegate (Progress or Completion event handler) is invoked on the thread or context appropriate for the application model. This whole paragraph can be summed up as this high level view:

High Level View of the typical use of a BackgroundWorker

But don’t take my word for it! The source code for the BackgroundWorker class is available here.

Now I promised that I would say something about console applications and Windows Services. Since console applications and Windows Services only make use of the default synchronization context, the BackgroundWorker behaves quite differently. Since there is no UI synchronization context, the RunWorkerCompleted and ProgressChanged event handlers are executed on ThreadPool threads (instead of being marshaled to the UI thread – which would be impossible since there is none). The same concept holds for the case when a background worker spins off another background worker. Since the background worker is operating within the default synchronization context on a ThreadPool thread, there is no UI synchronization context for the RunWorkerAsync method to grab using AsyncOperationManager and therefore we find ourselves in much the same situation.

Conclusion

I hope this article gave you a quick and concise look at the BackgroundWorker class of the .NET Framework. We did an overview of the class, went through an example and took a quick look at the inner workings of the class to hopefully shed some additional meaningful insight on the subject.

Code of the example application is available here.