@bobnoordam

Using backgroundworkers with progress reporting and cancelation

With the release of VS11 around the corner and the new async features in there everyone will have a wide toolset for writing non blocking responsive applications. If you have not yet played with the beta versions or regular threads (see for example Threads and the threadpool then the backgroundworker is the most basic component to help you get started.

Minimal example with parameter passing and error handling

This examples shows how to:

  • Run a progress in the background, and pass a parameter to it
  • Retrieve a result from the background process once completed
  • Check if there was an error during execution
  • Check if the background worker is idle before starting it
  • Prevent closing of the application while the background thread is active

As you will see, the amount code to accomplish all this is extremely limited:

using System;
using System.ComponentModel;
using System.Windows.Forms;
  
namespace BackgroundWorkerExample1
{
  
    /// <summary>
    /// Minimal example for use of a backgroundworker to do long running tasks without blocking
    /// the gui thread. This example consists of a single form, with a single button on it.
    /// </summary>
    public partial class Form1 : Form
    {
  
        private BackgroundWorker worker;                // the background worker component
  
        public Form1()
        {
            InitializeComponent();
  
            // Setup the background worker component, linking it to the procedures
            // to run for the work and completion events
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(WorkerDoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCompleted);
        }
  
        // button clicked by the user
        private void buttonStart_Click(object sender, EventArgs e)
        {
  
            // Refuse to run if already started, a background worker does not run
            // multiple instances simultaniously
            if (worker.IsBusy)
            {
                MessageBox.Show("Cant start now, already running!");
                return;
            }
  
            // Start the background process with a given number of loops. You can pass any
            // paramters, as long as it is just one. If you have multiple paramters to pass
            // put them into a class object.
            int numloops = 10;
            worker.RunWorkerAsync(numloops);
  
        }
  
        // Contains the actual workload to be process in the background
        private void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            int numloops = (int) e.Argument;
            for (int i=0; i < numloops; i++)
            {
                System.Threading.Thread.Sleep(1000);
            }
            e.Result = numloops;
        }
  
        // Contains the code to execute once the background task is completed,
        // check for erros, place results in the gui, etc.
        private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Check if the worker completed without errors
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
  
            // Cast back and display the return parameter
            int result = (int) e.Result;
            MessageBox.Show("Result: " + result);
        }
  
        // Handle the close event of the form. We dont want to allow closing
        // halfway an operation, so block the close action while the worker
        // is still active
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (worker.IsBusy)
            {
                e.Cancel = true;
            }
        }
  
    }
}

Example with progress reporting

The next code sample is the same application, but reworked to report back with its progress. A progress bar has been added on the main form, which will fill in 10 steps. As with the initial backgroundworker this behaviour is extremely easy to setup. The essential part is setting up an additional handler for the progress reporting event. After that a simple call is available to pass the completion % of the worker up to the gui.

using System;
using System.ComponentModel;
using System.Windows.Forms;
  
namespace BackgroundWorkerExample1
{
  
    /// <summary>
    /// Minimal example for use of a backgroundworker to do long running tasks without blocking
    /// the gui thread. This example consists of a single form, with a single button on it.
    /// </summary>
    public partial class Form1 : Form
    {
  
        private BackgroundWorker worker;                // the background worker component
  
        public Form1()
        {
            InitializeComponent();
  
            // Setup the background worker component, linking it to the procedures
            // to run for the work and completion events
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(WorkerDoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCompleted);
              
            // setup progress reporting
            worker.WorkerReportsProgress = true;   
            worker.ProgressChanged += new ProgressChangedEventHandler(WorkerChangedProgress);
        }
  
        // button clicked by the user
        private void buttonStart_Click(object sender, EventArgs e)
        {
  
            // Refuse to run if already started, a background worker does not run
            // multiple instances simultaniously
            if (worker.IsBusy)
            {
                MessageBox.Show("Cant start now, already running!");
                return;
            }
  
            // Setup the initial state of the progress bar
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Value = 0;
  
            // Start the background process with a given number of loops. You can pass any
            // paramters, as long as it is just one. If you have multiple paramters to pass
            // put them into a class object.
            int numloops = 10;
            worker.RunWorkerAsync(numloops);
  
        }
  
        // Contains the actual workload to be process in the background
        private void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            int numloops = (int) e.Argument;
            for (int i=0; i < numloops; i++)
            {
                System.Threading.Thread.Sleep(1000);
                worker.ReportProgress(i*10); // send back our progress status
            }
            e.Result = numloops;
            worker.ReportProgress(100); // send back 100% completion status
        }
  
        // Contains the code to execute once the background task is completed,
        // check for erros, place results in the gui, etc.
        private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Check if the worker completed without errors
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
  
            // Cast back and display the return parameter
            int result = (int) e.Result;
            MessageBox.Show("Result: " + result);
        }
  
        // Update the gui with the reported progress
        private void WorkerChangedProgress(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = (int) e.ProgressPercentage;
        }
  
        // Handle the close event of the form. We dont want to allow closing
        // halfway an operation, so block the close action while the worker
        // is still active
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (worker.IsBusy)
            {
                e.Cancel = true;
            }
        }
  
  
    }
}

Example with progress reporting and cancelation

Now the only thing left to desire is cancellation. Below is our little programm again with an addition CANCEL button on the form. Again, only minimal changes to the backgroundworker are needed. In the work loop a check is made to see if cancelation has been requested, and in the completion event again a check is made to see if the worker was allowed to run til completion or was canceled beforehand.

using System;
using System.ComponentModel;
using System.Windows.Forms;
  
namespace BackgroundWorkerExample1
{
  
    /// <summary>
    /// Minimal example for use of a backgroundworker to do long running tasks without blocking
    /// the gui thread. This example consists of a single form, with a single button on it.
    /// </summary>
    public partial class Form1 : Form
    {
  
        private BackgroundWorker worker;                // the background worker component
  
        public Form1()
        {
            InitializeComponent();
  
            // Setup the background worker component, linking it to the procedures
            // to run for the work and completion events
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(WorkerDoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCompleted);
              
            // setup progress reporting
            worker.WorkerReportsProgress = true;   
            worker.ProgressChanged += new ProgressChangedEventHandler(WorkerChangedProgress);
  
            // setup cancelation
            worker.WorkerSupportsCancellation = true;
  
        }
  
        // button clicked by the user
        private void buttonStart_Click(object sender, EventArgs e)
        {
  
            // Refuse to run if already started, a background worker does not run
            // multiple instances simultaniously
            if (worker.IsBusy)
            {
                MessageBox.Show("Cant start now, already running!");
                return;
            }
  
            // Setup the initial state of the progress bar
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Value = 0;
  
            // Start the background process with a given number of loops. You can pass any
            // paramters, as long as it is just one. If you have multiple paramters to pass
            // put them into a class object.
            int numloops = 10;
            worker.RunWorkerAsync(numloops);
  
        }
  
        // Contains the actual workload to be process in the background
        private void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            int numloops = (int) e.Argument;
            for (int i=0; i < numloops; i++)
            {
                System.Threading.Thread.Sleep(1000);
                worker.ReportProgress(i*10); // send back our progress status
  
                // check if the user wants us to cancel
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }
  
            }
            e.Result = numloops;
            worker.ReportProgress(100); // send back 100% completion status
        }
  
        // Contains the code to execute once the background task is completed,
        // check for erros, place results in the gui, etc.
        private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Check if the worker completed without errors
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
                return;
            }
  
            // Check if the operation was canceled by the user
            if (e.Cancelled)
            {
                MessageBox.Show("Aborted by the user!");
                return;
            }
  
            // Cast back and display the return parameter
            int result = (int) e.Result;
            MessageBox.Show("Result: " + result);
        }
  
        // Update the gui with the reported progress
        private void WorkerChangedProgress(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = (int) e.ProgressPercentage;
        }
  
        // Handle the close event of the form. We dont want to allow closing
        // halfway an operation, so block the close action while the worker
        // is still active
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (worker.IsBusy)
            {
                e.Cancel = true;
            }
        }
  
        // This is the code executed when teh cancel button is pressed by the user
        private void buttonCancel_Click(object sender, EventArgs e)
        {
  
            // if the worker is currently running, set its cancelation flag to stop it
            if (worker.IsBusy)
            {
                worker.CancelAsync();
            }
        }
  
  
    }
}

Downloads