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(); } } } }