How to Use BackgroundWorker for Your Threading Needs

When your application needs to perform time-consuming operations, you don’t want the interface to hang, so you need to use a separate thread for this operation. Still, if the operation takes some time to process, you may want to give the user some feedback about its progress. The .NET framework provides the Thread and ThreadPool classes to create and manage threads. They can be quite powerful and are a must for heavy threading operations. However, if you want a simpler solution, the BackgroundWorker class is your friend.

The Basics

BackgroundWorker uses the ThreadPool to manage threads, but makes your life much easier. For this tutorial, we will create a simple interface with one button named myButton and a progress bar named myProgressBar (make sure its maximum value is 100). Now, when clicking the button, we create a new BackgroundWorker, assign it something to do and launch it.

private void myButton_Click(object sender, EventArgs e)
{
  BackgroundWorker worker = new BackgroundWorker();
  // Assigns something to do
  worker.DoWork += new DoWorkEventHandler(worker_DoWork);

  // Launches the worker
  worker.RunWorkerAsync();
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  // Time-consuming stuff
  long dummy = 0;

  for (int i = 0; i < 100; i++)
  {
    for (int j = 0; j < 10000; j++)
      dummy += i * j;
  }
}

The DoWork event is assigned a new callback method. The RunWorkerAsync method actually launches the worker, creating a new thread and calling our callback method within that thread.

Sending Parameters to the Callback Method

The RunWorkerAsync can also take any object as a parameter. If you need to send more than one parameter, that object can be an array of objects. In the callback method, you will be able to access the object using e.Argument.

private void myButton_Click(object sender, EventArgs e)
{
  BackgroundWorker worker = new BackgroundWorker();

  // Assigns something to do
  worker.DoWork += new DoWorkEventHandler(worker_DoWork);

  // Launches the worker with a parameter
  worker.RunWorkerAsync("Hello, world!");
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  // Prints out the parameter
  Console.WriteLine(e.Argument.ToString());

  // Time-consuming stuff
  long dummy = 0;

  for (int i = 0; i < 100; i++)
  {
    for (int j = 0; j < 10000; j++)
      dummy += i * j;
  }
}

Retrieving the Result

When the work is done, you may have a result to send back to the main thread (and maybe display it somewhere). Let’s simply use a MessageBox to let the user know.

private void myButton_Click(object sender, EventArgs e)
{
  BackgroundWorker worker = new BackgroundWorker();

  // Assigns something to do
  worker.DoWork += new DoWorkEventHandler(worker_DoWork);

  // A method to call when the work is done
  worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

  // Launches the worker with a parameter
  worker.RunWorkerAsync("Hello, world!");
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  // Prints out the parameter
  Console.WriteLine(e.Argument.ToString());

  // Time-consuming stuff
  long dummy = 0;

  for (int i = 0; i < 100; i++)
  {
    for (int j = 0; j < 10000; j++)
      dummy += i * j;
  }

  e.Result = dummy;
}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // Lets the user know the result
  MessageBox.Show(e.Result.ToString());
}

RunWorkerCompleted takes a new callback method that will be called once the work is done. By the way, this method is called within the main thread, which means you can update any interface control without having to bother using Invoke. To pass on the work’s result to this method, we assign it to e.Result at the end of worker_DoWork. It is then accessible in the worker_RunWorkerCompleted method as e.Result. This result can be any object too, so it can hold pretty much anything you want.

Reporting Progress

The last step is to update our progress bar so the user knows what is going on.

private void myButton_Click(object sender, EventArgs e)
{
  BackgroundWorker worker = new BackgroundWorker();

  // Assigns something to do
  worker.DoWork += new DoWorkEventHandler(worker_DoWork);

  // A method to call when the work is done
  worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

  // Enables progress reporting
  worker.WorkerReportsProgress = true;
  worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);

  // Launches the worker with a parameter
  worker.RunWorkerAsync("Hello, world!");
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
  // Prints out the parameter
  Console.WriteLine(e.Argument.ToString());

  // Reference to our worker
  BackgroundWorker worker = (BackgroundWorker)sender;

  // Time-consuming stuff
  long dummy = 0;

  for (int i = 0; i < 100; i++)
  {
    for (int j = 0; j < 10000; j++)
      dummy += i * j;

    // Reports current progress in percent
    worker.ReportProgress(i + 1);
  }

  e.Result = dummy;
}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  // Lets the user know the result
  MessageBox.Show(e.Result.ToString());

  // Resets the progress bar
  myProgressBar.Value = 0;
}

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  // Updates the progress bar
  myProgressBar.Value = e.ProgressPercentage;
}

To enable progress reporting, we need to set WorkerReportsProgress to true and assign a new callback method to ProgressChanged. This method will be called within the main thread whenever we call the worker’s ReportProgress method. This method takes an integer from 0 to 100 as a parameter. It is the current progress in percent. In our example, we call it in the loop at every percent, then we assign its value to the progress bar using by retrieving it from e.ProgressPercentage. Note that ProgressChanged can also take any object as a second parameter that will be accessible from e.UserState (useful if you need to report more than the percentage).

BackgroundWorker can be a really nice tool to help implement simple threading. You could even manage multiple BackgroundWorkers at the same time. However, if it gets too heavy, you may want to consider using ThreadPool instead for better performances.