Apps

A GUI supporting .NET's BackgroundWorker for task progress and cancellation

In this hands-on programming tip, Justin James provides a sample project that you can modify when you're working with .NET's BackgroundWorker class.

 

Whenever I talk about multithreading and asynchronous and parallel programming, someone asks about task cancellation; specifically, Windows Forms applications that support a Cancel button and a progress bar and perform the actual work separate from the UI thread so the application doesn't appear to hang. I can understand why this question comes up a lot.

While .NET contains a built-in component (the BackgroundWorker class) for performing this work, the documentation is not very clear. I've had the "pleasure" of implementing this pattern, and it's complex enough that I find myself doing a copy/paste from other projects to save myself the effort.

I'll walk you through the process of creating a simple form application with this functionality. You're more than welcome to skip ahead and grab the source code, which is distributed under the MIT License.

Let's start by creating a basic form application. Add to the form two buttons (runButton and cancelButton), a progress bar (progressBar), a label (resultLabel), and a BackgroundWorker object (backgroundWorker). Give the buttons friendly names and clear the Text property of resultLabel. Also, look at the properties of backgroundWorker in the designer, and set "WorkerReportsProgress" and "WorkerSupportsCancellation" to True. Your form should look something like this:

Now we'll create some very basic logic to get the application up and running. Keep in mind that the asynchronous processing performed by BackgroundWorker has no way of getting parameters in or out. To compensate for this weakness, we'll create a worker (or processor) class that will have public properties that represent the input values for the work and the output of the work. This worker class will be instantiated as a private variable in the form application (called "_processor") so the input can be set by the calling routine, and the results can be pulled out by the post execution code. Here's what a simple worker class looks like:

using System;

using System.ComponentModel;

using System.Security.Cryptography;

using System.Threading;
namespace AsynchronousProcessingDemo

{

class Processor

{

public int Results { get; private set; }

public void PerformWorkAsync(BackgroundWorker worker, DoWorkEventArgs workerArgs)

{

worker.ReportProgress(0);
///TODO: fill in your own work, cancellation logic, and percent completion calculations
for (int counter = 0; counter <= 100; counter++)
{
if (worker.CancellationPending)
{
//Cancellation requested, cleanly exit
worker.ReportProgress(100);
workerArgs.Cancel = true;
break;
}
Thread.Sleep(250);

worker.ReportProgress(counter);

}
var randomNumber = new byte[1];
var rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomNumber);
Results = Convert.ToInt32(randomNumber[0]);
}
}
}

This is a very basic class. If you need input parameters for the work, create public properties for each parameter and read them as needed in the PerformWorkAsync method. (Note that I am stashing the output in a public property as well; you'll see why when we return to our main form.)

The next thing we need to do is to write the functions that encapsulate the concepts of "starting work," "cancelling work," and "cleaning up after work." We also need a way to update the progress bar. For this, we have four methods in the main form class:

private void Execute()

{

runButton.Enabled = false;

cancelButton.Enabled = true;

Cursor = Cursors.WaitCursor;

cancelButton.Cursor = Cursors.Default;

backgroundWorker.RunWorkerAsync();

}
private void CancelExecution()

{

backgroundWorker.CancelAsync();

cancelButton.Enabled = false;

}
private void PostExecutionCleanup(bool cancelled)

{

cancelButton.Enabled = false;

runButton.Enabled = true;

Cursor = Cursors.Default;
if (cancelled)

{

resultLabel.Text = "Cancelled";

}

else

{

resultLabel.Text = _processor.Results.ToString();

}

}
private void UpdateProgressBar(int progress)

{

progressBar.Value = progress;

}

Let's take a closer look at what is happening in each method. In Execute(), we disable the form (except for the Cancel button) while the work is occurring. Then we call the RunWorkerAsync() method of the backgroundWorker component. When RunWorkerAsync() is called, it fires the DoWork event, which we will handle in a moment. This is where we trigger the actual processing logic. The CancelExecution() method signals backgroundWorker to perform a cancellation and disables the Cancel button on the screen (to prevent double-clicking and to let the user know that something is occurring). PostExecutionCleanup() is called whenever the work is finished, regardless of whether it was allowed to run until the end or cancelled by the user. The UpdateProgressBar() method just sets the value of the onscreen progress bar.

Our final task is to wire event handlers to the on-form components and have those event handlers call the appropriate methods. All of these event handlers are the default event handlers (created by double clicking the component in the designer), except for the ProgressChanged and RunWorkerComplete events attached to backgroundWorker. To easily create those events, select the component in the designer, click the events "lightning bolt" in the "Properties" box, type the name of the method you want to use for the event handler, and press "Enter." This will create the empty method and attach it to the component's event in the designer code for you.

private void runButton_Click(object sender, EventArgs e)

{

Execute();

}
private void cancelButton_Click(object sender, EventArgs e)

{

CancelExecution();

}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{

_processor = new Processor();

_processor.PerformWorkAsync(sender as BackgroundWorker, e);

}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UpdateProgressBar(e.ProgressPercentage);
}
private void backgroundWorker_RunWorkerComplete(object sender, RunWorkerCompletedEventArgs e)

{

PostExecutionCleanup(e.Cancelled);

}

Now you have a fully functional application that acts like a modern desktop application. This particular application's functionality isn't terribly useful; all it does is waste time before producing a random number. All the same, it is a snap to take this sample project, adapt the Processor class to do what you need, and modify the form to suit your needs.

J.Ja

Disclosure of Justin's industry affiliations: Justin James has a contract with Spiceworks to write product buying guides. He is also under contract to OpenAmplify, which is owned by Hapax, to write a series of blogs, tutorials, and other articles.

---------------------------------------------------------------------------------------

Get weekly development tips in your inbox Keep your developer skills sharp by signing up for TechRepublic's free Web Developer newsletter, delivered each Tuesday. Automatically subscribe today!

About

Justin James is the Lead Architect for Conigent.

11 comments
Tony Hopkinson
Tony Hopkinson

memory of doing this once or twice. :p Good no frills example.

Justin James
Justin James

Have you done something like this before, where you do your processing off of the main thread? If so, how did you go about it? J.Ja

Justin James
Justin James

Glad you liked the example. It's one of those things where I find myself copying an existing project and deleteing 99% of the code in it, when I really just need to start from a shell of a project and adding to it as needed. :) J.Ja

Saurondor
Saurondor

Asynchronous update of the GUI can be an issue. How does .NET handle that?

Tony Hopkinson
Tony Hopkinson

with most of the plumbing and then just inherit from it. Stick a few doc comments in there and Bob's your mother's sister's brother.

Justin James
Justin James

... doing that, as a *general* rule. It's a really ugly pattern, when you think about it, because the worker needs to have knowledge of the GUI, which makes your code very tightly coupled. But, let's say you did have a legit need for it. In that case, the way I would do it, would be to give the worker a property for the form and set that property to the form. Next, there is the issue of making calls to the form from a thread that is not the GUI thread, which throws an exception. To get around that, you use the Invoke method of the form, and pass to it a delegate to the form's method that you are trying to call. It's a very, very icky way of doing things, and as a result, I avoid it like the plague. It's not always possible to avoid it (for example, handling events fired by the worker object, should you put any in, will be handled by the form on the worker's thread, not the UI thread, and will need to follow that pattern). If you'd like, I can email you the gigantic article I wrote on the topic for MSDN Magazine last year (never got published, my decision... it's a long story) which shows how to go about the whole shebang. J.Ja

Justin James
Justin James

That's entirely possible, or create a "New Project Template" that sets it up. But that suddenly starts to feel like "real work". If I wanted to work hard, I would have become a ditch digger. Oh, wait... that's what being a developer is most of the time anyways... :) J.Ja

Justin James
Justin James

For updating the GUI, the only thread that can do that is the GUI thread; all other threads must use the Invoke() methods and pass a delegate to what they want to do, which requests that the GUI's thread perform the requested action. As such, those will automatically be queued if they come rapid fire, since the GUI thread can only do one thing at a time. But the example I showed is one where multiple threads can call the same function at once, in which case, locking is a *must* if simultaneous access can cause problems. J.Ja

Saurondor
Saurondor

Under Swing the solution is provided by SwingUtilities. And works by creating another thread which will be invoked asynchronously in the event dispatching thread. For example: SwingUtilities.invokeLater(new Runnable() { public void run() { progressBar.setValue(cval); } }); I guess .NET should have something similar to that. So you don't have to use an object to maintain thread lock. It's a good solution, but depends on everyone getting a lock prior to writing to the log. I'm not certain what would be the effect of such a lock on other events that could cause a redraw. For example dragging, resizing or changing background color.

Justin James
Justin James

You are correct in your assessment of the multiple workers with multiple progress bars; each event handler is bound to an individual worker, and would call update code that updates the appropriate progress bar. In terms of having multiple event handlers update a shared object, and not having them overrun each other, that's easy. Let's use the same example, but change "UpdateProgressBar()" to be, say, "UpdateLogFile(string message)". Here's what that function would look like: private object _lockObject = new object(); private void UpdateLogFile(string message) { lock (_lockObject) { // Perform the steps needed to update the log file } } The lock() block is a "critical section". What this means, is that only one thread may be running its contents (or any other block which was locked on the same object) at a time. Any other threads trying to enter that section of code (or again, any other section locking on the same object) will block until whoever is in there exits (including being tossed out by an exception). At that point, one of the waiting threads will enter. One minor point, is that there is no way to know which of the waiting threads will get to go in next, it is completely non deterministic (I beleive it implements a type of spin lock underneath), so do NOT treat it as a FIFO or LIFO queue of work! If you require something like that, then you would craft it; you can look at Monitor.Enter, for example. Alternatively, you could build a queue (or a stack) of delegates to the functions that are trying to be run, and have a tight loop pop them off and run them in order. Or even better, your custom action queue could internally fire a "new item" event which would be handled by code that runs it only if nothing is currently running, and a "item finished" event which would run the next item in the list if possible. That sounds like a potentially interesting article for me to write. :) J.Ja

Saurondor
Saurondor

I'm curious what happens when you have multiple background processes. The code seems to bind: private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { UpdateProgressBar(e.ProgressPercentage); } To the workers. In this case it is trivial as no two workers would update the same progressbar, but what if its a log output to screen(text area) or an image. Both could try to update and I don't see any clear way to manage this synchronization.

Editor's Picks