Threading with BackgroundWorker helper class
by Navaneeth.K.N
Jun 4th 08
You are developing an application which has a long running job. You run this job on the main thread and your GUI got hanged until the job finishes. Obviously you will think about running the process in a parallel thread which reports the work progress to main thread and display progress on the GUI. In this article we will discuss about using a helper class called BackgroundWorker which is included since .NET framework 2.0 to make multi threading more easier.
Introduction to threading
A small piece in execution can be called as a thread. Multithreading can be used to execute parallel tasks along with the other threads. Common example of multi threading is to keep GUI active when a long running process is executing.
System.Threading is the namespace where classes related to threading are kept. To start a thread, initialize
System.Threading.Thread instance and call it's
Start() method. Here is how we create a simple thread.
Note |
All the examples has System.Threading and System.ComponentModel namespaces included |
Code |
class Program { static void Main(string[] args) { Thread t = new Thread(new ThreadStart(Worker)); t.Start(); }
static void Worker() { Console.WriteLine("Hello from worker ! Current thread is {0}", Thread.CurrentThread.ManagedThreadId); } }
|
ThreadStart is a delegate which represents the method to be executed when thread starts.
Parameterizedthreadstart delegate can be used to pass any custom objects to the worker method.
Why BackgroundWorker class ?
Creating a new thread and executing is pretty simple job, then why do we need such a helper class ? Consider the following code
Code |
public partial class Form1 : Form { public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(this.Worker)); t.Start(); }
private void Worker() { this.label1.Text = "Hello"; } }
|
Above code attempts to update
label1 value from a new thread. This type of operations are not allowed and you will end up with the following error message
"Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on."
.NET framework don't allow cross-thread control updation which can be handled by modifying the worker method as mentioned below
Code |
delegate void WorkerInvoker(); private void Worker() { if (this.InvokeRequired) this.Invoke(new WorkerInvoker(Worker)); this.label1.Text = "Hello"; }
|
InvokeRequired returns TRUE, if the current thread is not the main thread who created the controls.
Invoke() sends the request to windows message pump for execution. This is a common scenario in a multi threaded application.
BackgroundWorker provides set of features to make all these easy.
Features of BackgroundWorker
- A cancel flag to abort the worker safely.
- Reporting progress, completion and cancellation.
- Implements
IComponent interface which helps control to add using the Visual Studio IDE. - Cross thread exception handling is not allowed in .NET, though,
BackgroundWorker provides methods to handle exceptions in the worker method - Able to update GUI controls.
Working example
For getting details about members of this class, I suggest you to read
this MSDN article.
Here is a working code which uses
BackgroundWorker.
Code |
namespace BackgroundWorkerSample { class Program { static BackgroundWorker worker = null; static void Main(string[] args) { worker = new BackgroundWorker(); // Hooking events worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); //Fired when progress changes worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); // Fired when worker completes or cancels // Setting worker bahaviour worker.WorkerReportsProgress = true; worker.WorkerSupportsCancellation = true;
// Doing the work worker.RunWorkerAsync(); // Start the work on DoWork event handler
Console.WriteLine("To cancel, press any key"); Console.ReadLine(); // Requesting cancelation worker.CancelAsync();
Console.Read(); }
static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) Console.WriteLine("Worker cancelled"); else if (e.Error != null) Console.WriteLine("There is an unhandled exception in the worker execution. Exception : {0}", e.Error.Message); else Console.WriteLine("Worker completed. Result is {0}", e.Result); }
static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { Console.WriteLine("Worker completed {0}% of work", e.ProgressPercentage); }
static void worker_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1; i <= 100; i = i + 30) { // Checking we have a cancelation request. If yes, canceling the operation if (worker.CancellationPending) { e.Cancel = true; return; } // Doing some work. Just to simulate, we are sleeping for one sec. Thread.Sleep(1000);
// Reporting progress worker.ReportProgress(i); } e.Result = "Success"; // Result takes an object type. This will be passed through RunWorkerCompletedEventArgs } } }
|
Well, in this example we have done the following steps to make the worker running
- Instantiated new instance of
BackgroundWorker - Hooked
DoWork, ProgressChanged and RunWorkerCompleted event handlers. ProgressChanged event will be fired when worker.ReportProgress() gets called. RunWorkerCompleted will be fired when the DoWork event handler finishes.
- Setting
worker.WorkerReportsProgress and worker.WorkerSupportsCancellation to "true". First one is needed if worker should report progress. A call to worker.ReportProgress() without setting this value to "true" will throw an exception. worker.WorkerSupportsCancellation should be true when worker needs to be canceled safely before completing it's work. - Called
worker.RunWorkerAsync() which fires the DoWork event handler.
I guess the rest of the code is self explanatory. When you run the example, it produces the following output.

Note |
BackgroundWorker uses a thread in the thread pool. So never call Thread.Sleep() as shown in the above example. I have used sleep here to simulate a long running process. It is advised to use cancellation mechanisms as described above instead of aborting thread like Thread.CurrentThread.Abort() since BackgroundWorker uses a thread pool thread.
|
How it works internally
If you are not interested in learning how
BackgroundWorker works and manages all the above described functionalities, please feel free to
skip this section. In this section, I will be explaining what's happening internally.
What happens when BackgroundWorker gets initialized
- Initializes the
WorkerThreadStartDelegate and supplies WorkerThreadStart method. - Initializes
SendOrPostCallBack delegate instance which will be used when worker finishes. - Initializes another instance of
SendOrPostCallBack delegate for reporting progress.
SendOrPostCallBack delegate comes in
System.Threading namespace. To learn more, read
this MSDN article.
Hooking event handlers
BackgroundWorker is inherited from
Component base class. When event handlers are hooked, it is added to the events list in the
Component class. Here is how
DoWork event looks like
Code |
public event DoWorkEventHandler DoWork { add { base.Events.AddHandler(doWorkKey, value); } remove { base.Events.RemoveHandler(doWorkKey, value); } }
|
Event handlers are added to the events list with a key. This key is a
readonly object which is initialized by default. This key is used to get the event handlers back from the events list.
RunWorkerAsync()
This is the method which fires
DoWork event handler. Following are the steps
- Checks worker is running, and throws exception if it is running.
- Sets running status to "true" and
CancellationPending to "false". - Initializes
AsyncOperation instance and calls BeginInvoke() on WorkerThreadStartDelegate instance. BeginInvoke() uses a thread pool thread to do the job.
Remember when
BackgroundWorker initializes, it sets
WorkerThreadStartDelegate instance to a method called
WorkerThreadStart. So the call to
BeginInvoke() explained in the final step will execute
WorkerThreadStart method.
WorkerThreadStart()
- Initializes
DoWorkEventArgs instance. - Calls virtual
OnDoWork method with DoWorkEventArgs instance as parameter. - If worker finishes the job without cancellation, it sets the
Result property. - Creates instance of
RunWorkerCompletedEventArgs and calls OperationCompleted() method on the AsyncOperation instance with SendOrPostCallBack instance and RunWorkerCompletedEventArgs instance as parameters.
Here is how this method looks like
Code |
private void WorkerThreadStart(object argument) { object result = null; Exception error = null; bool cancelled = false; try { DoWorkEventArgs e = new DoWorkEventArgs(argument); this.OnDoWork(e); if (e.Cancel) { cancelled = true; } else { result = e.Result; } } catch (Exception exception2) { error = exception2; } RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled); this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg); }
|
Note |
Call to OnDoWork virtual method is wrapped inside a try/catch block which helps worker to handle uncaught exceptions and supply that to worker completed event handler through RunWorkerCompletedEventArgs instance.
|
OnDoWork()
Code |
protected virtual void OnDoWork(DoWorkEventArgs e) { DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey]; if (handler != null) { handler(this, e); } }
|
OnDoWork() event takes the
DoWorkEventHandler instance form the base class event list and invoke it. So the event handler hooked by you in your program will get executed.
This method is marked as
virtual and can be overridden in another class which is derived from
BackgroundWorker. This provides another pattern for using
BackgroundWorker which will be discussed later in this article.
ReportProgress()
Code |
public void ReportProgress(int percentProgress, object userState) { if (!this.WorkerReportsProgress) { throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntReportProgress")); } ProgressChangedEventArgs arg = new ProgressChangedEventArgs(percentProgress, userState); if (this.asyncOperation != null) { this.asyncOperation.Post(this.progressReporter, arg); } else { this.progressReporter(arg); } }
|
- Checks progress reporting is enabled, if not throws error.
- Initializes
ProgressChangedEventArgs with progress percentage and the user state object. - Checks worker is performing asynchronous operation and call
Post() on AsyncOperation instance. This queues the request with the supplied SendOrPostCallBack instance to a thread pool thread, which will be posted to windows message pump later. So the progress changed event will be called on the thread in which controls are created.
For using
BackgroundWorker, you don't need to learn about how it works internally. But it's always good to have an idea on what's happening behind the scenes.
Creating custom worker
We have already discussed about using
BackgroundWorker class and it's internals. Sometimes we need to create custom workers which does a specific job.
BackgroundWorker is not sealed and allows inheritance. In this section we will discuss about creating custom workers by inheriting from
BackgroundWorker.
Assume you are developing a product uploading tool for your e-commerce store. This tool should read images and upload it to the remote server. We created
ProductUploader class which derives from
BackgroundWorker.
BackgroundWorker allows sub classes to override
OnDoWork(),
OnProgressChanged and
OnRunWorkerCompleted methods. Here is the code
Code |
namespace CustomBackgroundWorker { class Program { static void Main(string[] args) { ProductImageUploader uploader = new ProductImageUploader(); // Preparing images uploader.Images.Add("Image1"); uploader.Images.Add("Image2"); uploader.Images.Add("Image3"); uploader.Images.Add("Image4"); uploader.Images.Add("Image5"); // Start uploading uploader.StartUpload(); // Wait for user Console.Read(); } }
class ProductImageUploader : BackgroundWorker { /// <summary> /// Initializes ProductImageUploader /// </summary> public ProductImageUploader() { this.WorkerReportsProgress = true; this.WorkerSupportsCancellation = true; }
private List<string> _images = new List<string>(); /// <summary> /// Gets / Sets image file names to be uploaded /// </summary> public List<string> Images { get { return _images; } set { _images = value; } }
public void StartUpload() { this.RunWorkerAsync(); }
#region Overridden methods
protected override void OnDoWork(DoWorkEventArgs e) { base.OnDoWork(e); int uploaded = 0; foreach (string image in Images) { if (this.CancellationPending) { e.Cancel = true; return; } // Upload this image. Thread.Sleep(1000); // Incrementing upload count uploaded++; double progress = (double)uploaded / Images.Count; // Calculating progress percentage and reporting this.ReportProgress((int)(progress * 100), image); } }
protected override void OnProgressChanged(ProgressChangedEventArgs e) { base.OnProgressChanged(e); Console.WriteLine("Completed {0}%. Uploaded {1}", e.ProgressPercentage, e.UserState); }
protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e) { base.OnRunWorkerCompleted(e); if (e.Cancelled) Console.WriteLine("Worker canceled"); else if (e.Error != null) Console.WriteLine("There's an uncaught error. {0}", e.Error.Message); else Console.WriteLine("Image upload completed."); }
#endregion } }
|

When this class initializes, we set
WorkerReportsProgress and
WorkerSupportsCancellation to "true".
Images property represents image names which has to be uploaded. On the overridden
OnDoWork, we are doing the upload and reporting progress.
Conclusion
Threading is a complex subject.
BackgroundWorker helps to implement multi threading without knowing much about threading and synchronization mechanisms. In this article, we have seen the internals of
BackgroundWorker and two patterns it offers.
That's all about
BackgroundWorker. I enjoyed writing this article and I expect you will also find this helpful. If you liked the article, please vote for it and feel free to leave a comment on the article discussion board.
Discussions
Need to discuss about this article ?
Click here