Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

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 404.6K   15.8K   301   48
Using a BackgroundWorker Thread to improve responsiveness in your UI, support cancel and display progress.
Screenshot - app.gif

Introduction

This article is for novice and amateur developers who recognize areas of their application that could use threading but don't want to be burdened with some of the complexity that comes with threading. Threading is a concept that many programmers tend to avoid because it can be difficult to understand, debug and implement. You can develop a very sophisticated multi-threaded application using C#. Don't worry, the BackgroundWorker object makes threading easy to use even if you don't want to take the time to understand everything about threading.

Using the Code

When your application loads, it runs on a single thread. This is referred to as the UI thread. Generally this is the thread that all of your UI objects have been created on and this is the thread that all of your code execution is performed on. The UI also uses this single thread to paint the UI objects. So when you're running a long task, like processing some unknown number of MP3 files in a directory, your application locks up, the window turns white, the user can't click any buttons, the title bar changes to "My Cool App (Not Responding)." So you go back and put in a bunch of Application.DoEvents() calls into your MP3 crunching function and all is better again… not really, the code runs slower now and the form still locks up but only for short spurts. The whole application seems a bit choppy.

What you need to do is this heavy processing on a different thread. Leave the UI thread free for painting the UI. .NET has made the BackgroundWorker object available to us to simplify threading. This object is designed to simply run a function on a different thread and then call an event on your UI thread when it's complete. The steps are simple, create a BackgroundWorker object, tell it what function to run on the background thread (the DoWork function), tell it what function to run on the UI thread when the work is complete (the RunWorkerCompleted function), then tell the BackgroundWorker object to go to work.

There is one rule you need to be aware of - you can't access UI objects on a thread that didn't create them. Therefore you would receive a runtime error if you wrote the line of code lblStatus.Text = "Processing file 5 of 100"; in the DoWork function. There are two ways around this and I use both in the examples. The BackgroundWorker object resolves this problem by giving us a ReportProgress function which can be called from the background thread's DoWork function, this will cause the ProgressChanged event to fire on the UI thread. Now we can access the UI objects on their thread and do what we want. But what if I just need to update a label or disable a button while on the background thread, no problem, using Control.Invoke you can supply some code (in an anonymous function) to be run on the UI thread, I use this technique in the asynchronous example to update the Progress Form's label and progress bar.

Synchronous Example

There are two examples in this article, one on synchronous threading and the other on asynchronous threading. The idea behind the synchronous example is that you want to disable your application so the user can't do anything else, but you want the application to still paint while you notify the user of the progress and give him or her the ability to cancel the process.

C#
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;

    #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 cancelled.
                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. Then we need to inspect our
        // response to see if an error occurred, a cancel was requested or
        // if we completed successfully.

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

        // Check to see if an error occurred 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
}

Asynchronous Example

The purpose of the asynchronous example is to allow the user to kick off a process while still having the ability to continue using your application. However, the user still needs to have the ability to determine the progress of the process and the ability to cancel it. In this example we don't want to display modal for that which disables the application.

C#
public partial class fmMain : Form
{
    // 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 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 = "Cancelling...";

            // 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 "Cancelling...", 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 a bit to demonstrate that there is time between
                // "Cancelling..." and "Cancel ed".
                Thread.Sleep( 1200 );

                // Set the e.Cancel flag so that the WorkerCompleted event
                // knows that the process was cancelled.
                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 occurred, a cancel was
        // requested or if we completed successfully.

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

        // Check to see if an error occurred 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 supplied 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
}

Points of Interest

Check out my other posts:

About the Author

Andrew D. Weiss
Software Engineer
Check out my blog: More-On C#

Image 2

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

 
GeneralRe: MY app hangs when closing... Pin
massimopasquali21-Feb-10 6:49
massimopasquali21-Feb-10 6:49 
GeneralRelated question Pin
segalindoa28-Jul-08 12:27
segalindoa28-Jul-08 12:27 
GeneralGood article--can use Designer Pin
Samuel Allen26-May-08 17:31
Samuel Allen26-May-08 17:31 
GeneralError without Thread.Sleep Pin
Keith Dennis14-Mar-08 7:32
Keith Dennis14-Mar-08 7:32 
GeneralRe: Error without Thread.Sleep Pin
Greg Cadmes14-Apr-08 9:38
Greg Cadmes14-Apr-08 9:38 
GeneralExcellent!! Pin
dac0029-Nov-07 13:47
dac0029-Nov-07 13:47 
QuestionThank you & question... Pin
Skcheng14-Oct-07 15:33
Skcheng14-Oct-07 15:33 
AnswerRe: Thank you & question... Pin
Clear Demon9-Nov-07 8:38
professionalClear Demon9-Nov-07 8:38 
GeneralNice clarification Pin
Chris Maunder8-Oct-07 7:40
cofounderChris Maunder8-Oct-07 7:40 
GeneralResponsive UI Pin
ptmcomp1-Oct-07 8:51
ptmcomp1-Oct-07 8:51 
GeneralRe: Responsive UI Pin
Johnno741-Oct-07 14:15
Johnno741-Oct-07 14:15 
GeneralFeedback Pin
Andrew D. Weiss1-Oct-07 4:43
Andrew D. Weiss1-Oct-07 4:43 
GeneralRe: Feedback Pin
GavinSV3-Oct-07 11:44
GavinSV3-Oct-07 11:44 
QuestionEvents? Pin
AndyCLon1-Oct-07 2:50
AndyCLon1-Oct-07 2:50 
AnswerRe: Events? Pin
Andrew D. Weiss1-Oct-07 4:40
Andrew D. Weiss1-Oct-07 4:40 
GeneralGreat! Pin
Alberto Bencivenni30-Sep-07 22:33
Alberto Bencivenni30-Sep-07 22:33 
GeneralNicely done. Pin
Alex D. Mawhinney27-Sep-07 8:33
Alex D. Mawhinney27-Sep-07 8:33 
GeneralRe: Nicely done. Pin
PIEBALDconsult27-Sep-07 8:47
mvePIEBALDconsult27-Sep-07 8:47 
GeneralTop rate article. Pin
joomlathug27-Sep-07 2:45
joomlathug27-Sep-07 2:45 
GeneralRe: Top rate article. Pin
GordonRudman1-Oct-07 5:25
GordonRudman1-Oct-07 5:25 
GeneralExcellent Article! Pin
Rammohan Raja25-Sep-07 11:47
Rammohan Raja25-Sep-07 11:47 
GeneralRe: Excellent Article! Pin
blackjack215026-Sep-07 1:41
blackjack215026-Sep-07 1:41 
GeneralRe: Excellent Article! Pin
kkaratasli11-Feb-08 10:04
kkaratasli11-Feb-08 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.