Click here to Skip to main content
15,886,799 members
Articles / Programming Languages / C#

BackgroundWorker Threads and Supporting Cancel

Rate me:
Please Sign up or sign in to vote.
4.89/5 (114 votes)
25 Sep 2007CPOL3 min read 405.6K   15.8K   301  
Using a BackgroundWorker Thread to improve responsiveness in your UI, support cancel and display progress.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace DemoCancel
{
	public partial class fmMain : Form
	{
		// The progress form will be created and shown modally while the
		// Synchronous process is running.  This form will notify the background
		// thread if a Cancellation is performed. The Background thread will update
		// the status label and ProgressBar on the Progress Form using Control.Invoke.
		private fmProgress m_fmProgress = null;

		// The BackgroundWorker will be used to perform a long running action
		// on a background thread.  This allows the UI to be free for painting
		// as well as other actions the user may want to perform.  The Background
		// thread will use the ReportProgress event to update the ProgressBar on the
		// UI thread.
		private BackgroundWorker m_AsyncWorker = new BackgroundWorker();

		public fmMain()
		{
			InitializeComponent();

			// Create a background worker thread that ReportsProgress & SupportsCancellation
			// Hook up the appropriate events.
			m_AsyncWorker.WorkerReportsProgress = true;
			m_AsyncWorker.WorkerSupportsCancellation = true;
			m_AsyncWorker.ProgressChanged += new ProgressChangedEventHandler( bwAsync_ProgressChanged );
			m_AsyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bwAsync_RunWorkerCompleted );
			m_AsyncWorker.DoWork += new DoWorkEventHandler( bwAsync_DoWork );
		}

		#region Synchronous BackgroundWorker Thread
		
		private void bnSync_Click( object sender, EventArgs e )
		{
			// Create a background thread
			BackgroundWorker bw = new BackgroundWorker();
			bw.DoWork += new DoWorkEventHandler( bw_DoWork );
			bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bw_RunWorkerCompleted );

			// Create a progress form on the UI thread
			m_fmProgress = new fmProgress();

			// Kick off the Async thread
			bw.RunWorkerAsync();

			// Lock up the UI with this modal progress form.
			m_fmProgress.ShowDialog( this );
			m_fmProgress = null;
		}

		private void bw_DoWork( object sender, DoWorkEventArgs e )
		{
			// Do some long running task...
			int iCount = new Random().Next( 20, 50 );
			for( int i = 0; i < iCount; i++ )
			{
				// The Work to be performed...
				Thread.Sleep( 100 );

				// Update the description and progress on the modal form
				// using Control.Invoke.  Invoke will run the anonymous
				// function to set the label's text on the UI thread.  
				// Since it's illegal to touch the UI control on the worker 
				// thread that we're on right now.
				// Moron Anonymous functions: http://www.codeproject.com/books/cs2_anonymous_method.asp
				m_fmProgress.lblDescription.Invoke(
					(MethodInvoker) delegate()
					{
						m_fmProgress.lblDescription.Text = "Processing file " + i.ToString() + " of " + iCount.ToString();
						m_fmProgress.progressBar1.Value = Convert.ToInt32( i * ( 100.0 / iCount ) );
					}
				);
				
				// Periodically check for a Cancellation
				// If the user clicks the cancel button, or tries to close
				// the progress form the m_fmProgress.Cancel flag will be set to true.
				if( m_fmProgress.Cancel )
				{
					// Set the e.Cancel flag so that the WorkerCompleted event
					// knows that the process was canceled.
					e.Cancel = true;
					return;
				}
			}
		}

		private void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
		{
			// The background process is complete. First we should hide the
			// modal Progress Form to unlock the UI. The we need to inspect our
			// response to see if an error occured, a cancel was requested or
			// if we completed succesfully.

			// Hide the Progress Form
			if( m_fmProgress != null )
			{
				m_fmProgress.Hide();
				m_fmProgress = null;
			}

			// Check to see if an error occured in the 
			// background process.
			if( e.Error != null )
			{
				MessageBox.Show( e.Error.Message );
				return;
			}

			// Check to see if the background process was cancelled.
			if( e.Cancelled )
			{
				MessageBox.Show( "Processing cancelled." );
				return;
			}

			// Everything completed normally.
			// process the response using e.Result
			MessageBox.Show( "Processing is complete." );
		}

		#endregion

		#region Asynchronous BackgroundWorker Thread

		private void bnAsync_Click( object sender, EventArgs e )
		{
			// If the background thread is running then clicking this
			// button causes a cancel, otherwise clicking this button
			// launches the background thread.
			if( m_AsyncWorker.IsBusy )
			{
				bnAsync.Enabled = false;
				lblStatus.Text = "Canceling...";

				// Notify the worker thread that a cancel has been requested.
				// The cancel will not actually happen until the thread in the 
				// DoWork checks the bwAsync.CancellationPending flag, for this
				// reason we set the label to "Canceling...", because we haven't 
				// actually cancelled yet.
				m_AsyncWorker.CancelAsync();
			}
			else
			{
				bnAsync.Text = "Cancel";
				lblStatus.Text = "Running...";

				// Kickoff the worker thread to begin it's DoWork function.
				m_AsyncWorker.RunWorkerAsync();
			}
		}

		private void bwAsync_DoWork( object sender, DoWorkEventArgs e )
		{
			// The Sender is the BackgroundWorker object we need it to
			// report progress and check for cancellation.
			BackgroundWorker bwAsync = sender as BackgroundWorker;

			// Do some long running operation here
			int iCount = new Random().Next( 20, 50 );
			for( int i = 0; i < iCount; i++ )
			{
				// Working...
				Thread.Sleep( 100 );

				// Periodically report progress to the main thread so that it can
				// update the UI.  In most cases you'll just need to send an integer
				// that will update a ProgressBar, but there is an OverLoad for the
				// ReportProgress function so that you can supply some other information
				// as well, perhaps a status label?
				bwAsync.ReportProgress( Convert.ToInt32( i * ( 100.0 / iCount ) ) );

				// Periodically check if a Cancellation request is pending.  If the user
				// clicks cancel the line m_AsyncWorker.CancelAsync(); if ran above.  This
				// sets the CancellationPending to true.  You must check this flag in here and
				// react to it.  We react to it by setting e.Cancel to true and leaving.
				if( bwAsync.CancellationPending )
				{
					// Pause for bit to demonstrate that there is time between 
					// "Cancelling..." and "Canceled".
					Thread.Sleep( 1200 );

					// Set the e.Cancel flag so that the WorkerCompleted event
					// knows that the process was canceled.
					e.Cancel = true;
					return;
				}
			}
			bwAsync.ReportProgress( 100 );
		}

		private void bwAsync_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
		{
			// The background process is complete. We need to inspect 
			// our response to see if an error occured, a cancel was 
			// requested or if we completed succesfully.

			bnAsync.Text = "Start Long Running Asychronous Process";
			bnAsync.Enabled = true;

			// Check to see if an error occured in the 
			// background process.
			if( e.Error != null )
			{
				MessageBox.Show( e.Error.Message );
				return;
			}

			// Check to see if the background process was cancelled.
			if( e.Cancelled )
			{
				lblStatus.Text = "Cancelled...";
			}
			else
			{
				// Everything completed normally.
				// process the response using e.Result

				lblStatus.Text = "Completed...";
			}
		}

		private void bwAsync_ProgressChanged( object sender, ProgressChangedEventArgs e )
		{
			// This function fires on the UI thread so it's safe to edit
			// the UI control directly, no funny business with Control.Invoke.
			// Update the progressBar with the integer supplide to us from the 
			// ReportProgress() function.  Note, e.UserState is a "tag" property
			// that can be used to send other information from the BackgroundThread
			// to the UI thread.
			
			progressBar1.Value = e.ProgressPercentage;
		}

		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) BrightStar Partners
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions