Login | Register
Nerd ParadiseArtisanal tutorials since 1999
There are lots of factors you have to consider when creating a multi-threaded program in .NET. Going about sharing memory directly will cause a crash without setting up everything just right. However, a lot of this work is conveniently bundled up and abstracted away from you in a .NET class called BackgroundWorker.

The BackgroundWorker does exactly as its title implies. It works in the background in a separate thread. This is great if you have some complex calculation or maybe you're connecting to a slow server from your application and it blocks the UI thread. However, the background worker is not ideal if you need constant communication between the two threads.

The background worker is set up such that it has several events attached to it that you add handlers to.

Here is a simple example that simulates performing a complex calculation using Thread.Sleep. This "calculation" takes 10 seconds to perform, which is plenty of time for Windows to think that the application is hanging if the user wildly clicks while the computer is churning numbers. In this example, the user presses the button which will then show a progress counter from 0 to 100% and then displays the result of the calculation.

In this example, I've created a new Windows Forms application in Visual Studio with a button (with the default name of button1) and a label (with the default name of label1).


We click on the button...


...and it shows us progress...


...and then the final result when it's done.

A more detailed explanation of the BackgroundWorker's handlers and event args is discussed below the code.


using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace BackgroundWorkerExample
{
    public partial class Form1 : Form
    {
        private BackgroundWorker bw;

        public Form1()
        {
            InitializeComponent();

            this.bw = new BackgroundWorker();
            this.bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            this.bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
            this.bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            this.bw.WorkerReportsProgress = true;
            
            this.button1.Click += new EventHandler(button1_Click);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (!this.bw.IsBusy)
            {
                this.bw.RunWorkerAsync();
                this.button1.Enabled = false;
            }
        }

        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.label1.Text = "The answer is: " + e.Result.ToString();
            this.button1.Enabled = true;
        }

        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.label1.Text = e.ProgressPercentage.ToString() + "% complete";
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = (BackgroundWorker)sender;
            for (int i = 0; i < 100; ++i)
            {
                // report your progres
                worker.ReportProgress(i);

                // pretend like this a really complex calculation going on eating up CPU time
                System.Threading.Thread.Sleep(100);
            }
            e.Result = "42";
        }
    }
}


The DoWork Event

The handler you add to this event will be run in a separate thread. The event args (DoWorkEventArgs) has two properties that are used to pass parameters to the background thread and send results back to the main thread.

  • Argument - an object. This is the argument you pass in when you start the work. I'll show you how to use this later.
  • Result - an object. When you finish the work, you want to set this to the result you calculated.

The RunWorkerCompleted Event

When the background worker finishes its work, this event will be fired in the original thread. The RunWorkerEventArgs also has a property called Result which is set to the value you set Result to in the DoWorkEventArgs in the DoWork handler mentioned above.

The ProgressedChanged Event

"Please wait..." isn't exciting at all. Up-to-the-second feedback can be created with the ProgressChanged event. The sender object of the DoWork event is the background worker itself. When you call ReportProgress in the DoWork handler, the ProgressChanged event is fired in the main thread. The ProgressChangedEventArgs has a property called ProgressPercentage, being the value you passed in to ReportProgress.

RunWorkerAsynch Method

This is how the DoWork event gets kicked off. RunWorkerAsynch, as you can probably guess, does not block the current thread's execution. It'll make the BackgroundWorker fire the DoWork event in a new thread, however, it's best to check the IsBusy property otherwise you'll get a crash if the worker as already been started. This could happen if, for example, you took out the code to disable the button above. Additionally, you can also pass an object into the RunWorkerAsynch method and it will appear in the DoWorkEventArgs as the Argument property mentioned above.

So now you have absolutely no excuse to block a UI thread in .NET.