Working with Background Workers in C#

Have you handled multithreaded code in the apps that you write? I'm willing to bet that you likely have, especially these days when terms like "Full Stack Developer" basically mean, IT practitioner than can do just about any IT related role. Even if your primary role is a front end web developer, there's a high chance at some point in your career, you'll need to write a Windows service, or a small console mode tool or something other than you usual day to day code, and there's a good chance that tool will need to process reasonably large amounts of data.

If you're using the newer .NET 4 runtime, at your disposal you have a huge array of different techniques, ranging from raw threads themselves right through to the new Async & Task related stuff. Don't get me wrong; all of this functionality is incredibly powerful, and when used correctly will ensure that you leverage maximum benefit from it, especially when the underlying software supports it. Unfortunately, most of it also requires a very steep learning curve and some serious commitment to understanding it.

If your needs are somewhat simpler, such as perhaps a simple file processing routine, or a quick one off easy to do calculation that may take time to run, you might be forgiven for thinking there must be a simpler way. There is good news, and it's called the background worker. The good thing about the background worker class is that it can be used under winforms and WPF with little to no change in how it's implemented. If you read around on the web, however, you'll see a lot of people claiming this version is for winforms, and this version is for WPF, as is usual Stack Overflow has more than its fair share of fights over this.

Threading Made Simple

To actually use a background worker, you only need to reference system.componentmodel using the following code:

using System.ComponentModel;

Once this is done, you simply create an object as follows:

BackgroundWorker myWorker = new BackgroundWorker();

This works identically in both WinForms and WPF.

Once you have a background worker to play with, you can begin making it work for you by assigning events to its handlers, and then calling its run method.

A background worker exposes three different events for you to use:

  • Do Work
  • Progress Changed
  • Run Worker Completed

Do Work is called when your thread is started, and should be the routine where you want to perform your threaded task.

Progress Changed is called when your task has a progress update to report. This is ONLY called, however, if you set the appropriate flags on the BackgroundWorker object, and only when your task actually tells the object to trigger one (more on this in a moment).

Run Worker Completed is called when your threaded task returns, and is the point where you would signal to the caller that the task has finished, reporting any status that may have been returned.

A simple example of the background worker's use is as follows:

private bool _isRunning;
private BackgroundWorker backgroundWorker;

public Form1()
{
   InitializeComponent();

   backgroundWorker = new BackgroundWorker();

   backgroundWorker.DoWork += BackgroundWorkerDoWork;
   backgroundWorker.ProgressChanged +=
      BackgroundWorkerProgressChanged;
   backgroundWorker.RunWorkerCompleted +=
      BackgroundWorkerRunWorkerCompleted;

}

private void BtnActionClick(object sender,
   System.EventArgs e)
{
   if(!_isRunning)
   {
      backgroundWorker.RunWorkerAsync();
      return;
   }

   backgroundWorker.CancelAsync();
}

void BackgroundWorkerDoWork(object sender,
   DoWorkEventArgs e)
{
   _isRunning = true;
   // Do intensive task here
}

void BackgroundWorkerRunWorkerCompleted(object sender,
   RunWorkerCompletedEventArgs e)
{
   _isRunning = false;
}

BackgroundWorkerProgressChanged(object sender,
   ProgressChangedEventArgs e)
{
   // Update progress here
}

As previously mentioned, the snippet will work equally as well in WPF even though the preceding code is from a winforms app. You'll also see in the last code snippet that I've wired up the progress handler; however, if you attempt to use this as is, you'll find that you're unable to trigger a progress update, and/or cancel your task once it's running.

The reason for this is because, by default, the class does not enable the ability to send progress updates, nor does it enable the ability to cancel. Setting these flags, however, is extremely easy; you just need to extend the creation of the object as follows:

backgroundWorker = new BackgroundWorker
   { WorkerReportsProgress = true,
   WorkerSupportsCancellation = true };

Doing things this way turns on the progress and cancellation options, and claims any resources the class needs to handle them.

This is still not 100% of the story, though. To report progress updates, you need to tell your task to send updates to the calling application. For example, examine this piece of code:

int myProgress = calculateProgress();
// Get an integer from 0 to 100 representing % done
var myWorker = sender as BackgroundWorker;
myWorker.ReportProgress(myProgress);

This will result in your event handler for progress changed being triggered; then, you can get the value passed from the event arguments where you'll find a property called ProgressPercentage:

void BackgroundWorkerProgressChanged(object sender,
   ProgressChangedEventArgs e)
{
   int myPercantage = e.ProgressPercentage;
   // do something with myPercentage
}

This situation with cancellation is somewhat similar too.

To cancel your task, call the workers CancelAsync method EG:

backgroundWorker.CancelAsync();

Then, in your long running task, periodically check the cancellation pending flag using code similar to this:

void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
   // Do some stuff here
   var myWorker = sender as BackgroundWorker;
   if(myWorker.CancellationPending)
   {
      // Do any tidying up here
      return;
   }

   // Do more stuff here

   return;
}

The cancellation pending flag will be true if the caller has previously requested a cancel. Otherwise, it will remain false.

To start your task running, simply call the BackgroundWorkers RunWorkerAsync method, which optionally takes an object containing any data you want to pass to your task; for example:

var myData = new myDataObject();
backgroundWorker.RunWorkerAsync(myData);

myData is then available in your DoWork event handler as follows:

var myData = e.Argument as myDataObject;

And that's pretty much all there is to simple multithreading.

Remember, though, you will still need to take care of things, such as updating UI controls correctly to prevent cross thread exceptions, but as far as the basics of using a BackgroundWorker go it's really very simple to get going without any of the mess. There is one thing you'll need to remember, though. Exceptions that are thrown inside DoWork WILL NOT be passed to your user interface. If anything throws an exception inside your task, there is a very, very good chance that you won't know it happened. It's for this reason that BackgroundWorker allows you to pass a data object out when you return from your task.

In the event arguments passed to your handler, you'll find an object called 'Result'; this is a generic object you can cast to pretty much any class you have in your application. The recommended way of dealing with exceptions is to use standard exception handling methods inside your task, and if an abnormal condition is detected, assign some kind of notification to the Result property, and then simply just return as normal.

Got a burning question about .NET? Come and find me; I'm usually hovering around in Twitter as @shawty_ds and more often than not I can be found on Linked-In, in the .NET users group I help run, called "Lidnug", or simply just add some comments below. I'm always interested in hearing your ideas and subjects for this column.



Related Articles

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date