Click here to Skip to main content
Click here to Skip to main content

A tryst with Whidbey: Background operations in Windows Forms

, 11 Nov 2004
Rate this:
Please Sign up or sign in to vote.
This article talks about how to use the Background Worker component in Whidbey to execute long-running operation asynchronously. It also talks about how to support progress reporting and cancellation.

Sample Image - backgroundworker.jpg

Introduction

Whidbey comes along with many exciting features that every developer would love to use. One of the hottest new items it brings along is the Background Worker component. If you have ever written code to execute any asynchronous operation in Windows Forms, you would agree that there are a number of little things that we need to take care of – the most prominent one being that we should not access any controls in the UI from our worker thread. Bugs are common and hard to track when working with multiple threads.

The Background Worker component cleans our plate clean of all these responsibilities while implementing asynchronous operations. It gives us instead, a cool event-based mechanism to call long-running methods with no additional headaches. This article explores how we could use this component in a simple Windows Forms project.

A bit of history

During pre-Whidbey days, the most common and simplest approach for handling long processes was to use asynchronous delegate invocation. This basically involved a call to the BeginInvoke method of a delegate. Calling BeginInvoke will queue the method execution to be run from the system thread pool, returning immediately, without waiting for the method execution to complete. This ensured that the caller will not have to wait until the method finishes its task.

After invoking a method asynchronously, you would typically use the Control.InvokeRequired property and Control.BeginInvoke method to facilitate UI-worker thread communication. This was how we usually send progress status back to the UI thread to update the progress bar/status bar control in the UI.

The downside of this procedure is that we have to keep track of which thread we are on, and should ensure that we don’t call any UI control members within a worker thread. Typically, the structure for such a code involves the following snippet:

if (this.InvokeRequired)
{
    this.BeginInvoke(…the target…);
}
else
{
    // proceed performing tasks
}

Adding this snippet wherever UI-worker communication exists, would make the code illegible and tough to maintain. A few design patterns in .NET simplifies this code; but all this was a tall-order solution for a simple requirement – you just want to run a long process, and inform the user on the progress.

The Whidbey way

Enter Whidbey. Introducing the BackgroundWorker component which would save us our time and effort.

The BackgroundWorker component can be dropped onto your form from the Toolbox Components tab. It finds place in the component tray whose properties will be available in the Properties window.

The BackgroundWorker component exposes three events and three methods that you will be interested in. An overview of the sequence follows:

  1. You invoke the BackgroundWorker.RunWorkerAsync method, passing any argument if necessary. This raises the DoWork event.
  2. The DoWork handler will have the long-running code or a call to the long-running method. You retrieve any arguments passed through DoWork event arguments.
  3. When the long-running method finishes execution, you set the result to the DoWork event argument's Result property.
  4. The RunWorkerCompleted event is raised.
  5. In the RunWorkerCompleted event handler, you do post operation activities.

A detailed step-by-step description follows.

Running a process asynchronously

The RunWorkerAsync method starts an operation asynchronously. It takes an optional object argument which may be used to pass initialization values to the long-running method.

private void startAsyncButton_Click(System.Object sender, System.EventArgs e)
{
    // Start the asynchronous operation.
    backgroundWorker1.RunWorkerAsync(someArgument);
}

The RunWorkerAsync method raises the DoWork event, in whose handler you would put your long-running code. This event has a DoWorkEventArgs parameter, which has two properties – Argument and Result. The Argument property gets its value from the optional parameter we had set while calling RunWorkerAsync. The Result property is used to set the final result of our operation, which would be retrieved when the RunWorkerCompleted event is handled.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    e.Result = LongRunningMethod((int)e.Argument, worker, e);
}

Instead of referencing the backgroundWorker1 instance directly, we obtain a reference to it through the sender object. This ensures that when we have multiple instances of the BackgroundWorker component in our form, we obtain the instance which had actually raised the event.

We need to pass a reference of the BackgroundWorker instance as well as the event argument to the long running method to facilitate cancellation and progress reporting.

Retrieving the state after completion

The RunWorkerCompleted event is raised in three different circumstances; either the background operation completed, was cancelled, or it threw an exception. The RunWorkerCompletedEventArgs class contains the Error, Cancelled, and Result properties which could be used to retrieve the state of the operation, and its final result.

private void backgroundWorker1_RunWorkerCompleted(object sender, 
                                    RunWorkerCompletedEventArgs e)
{
    // First, handle the case where an exception was thrown.
    if (e.Error != null)
    {
        // Show the error message
    }
    else if (e.Cancelled)
    {
        // Handle the case where the user cancelled the operation.
    }
    else
    {
        // Operation completed successfully. 
        // So display the result.
    }
    // Do post completion operations, like enabling the controls etc.
}

Notifying progress to the UI

To support progress reporting, we first set the BackgroundWorker.WorkerReportsProgress property to true, and then attach an event handler to the BackgruondWorker.ProgressChanged event. The ProgressChangedEventArgs defines the ProgressPercentage property which we could use to set the value for say, a progress bar in the UI.

long LongRunningMethod(int someArgument, BackgroundWorker worker, DoWorkEventArgs e)
{
    // Do something very slow

    // Calculate and report the progress as a 
    // percentage of the total task
    int percentComplete = (currentValue * 100) / maxValue;
    worker.ReportProgress(percentComplete);

    // Return the result
    return result;
}

private void backgroundWorker1_ProgressChanged(object sender, 
                                  ProgressChangedEventArgs e)
{
    progressBar1.Value = e.ProgressPercentage;
}

Note that if your long-running method is by itself a recursive operation, then you will have to ensure that the progress percentage you are calculating is for the total task and not for the current iteration. The code for this will take the form as follows:

long LongRunningMethod(int n, BackgroundWorker worker, DoWorkEventArgs e)
{
    // Do something very slow

    // Calculate and report the progress as a 
    // percentage of the total task. Since this is
    // a recursive operation, check whether the 
    // percent just calculated is the highest - if yes,
    // it directly represents the percent of the total
    // task completed.
    int percentComplete = (currentValue * 100) / maxValue;
    if (percentComplete > highestPercentageReached)
    {
            highestPercentageReached = percentComplete;
            worker.ReportProgress(percentComplete);
    }

    // Return the result
    return result;
}

Supporting cancellation

To cancel a background worker process, we should first set the BackgroundWorker.WorkerSupportsCancellation property to true. When we need to cancel an operation, we invoke the BackgroundWorker.CancelAsync method. This sets the BackgroundWorker.CancellationPending property to true. In the worker thread, you have to periodically check whether this value is true to facilitate cancellation. If it is true, you set the DoWorkEventArgs.Cancel property to true, and skip executing the method. A simple if block will do great here.

private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e)
{
    // Cancel the asynchronous operation.
    backgroundWorker1.CancelAsync();

    // Do UI updates like disabling the Cancel button etc.
}

long LongRunningMethod(int n, BackgroundWorker worker, DoWorkEventArgs e)
{
    if (worker.CancellationPending)
    {
        e.Cancel = true;
    }
    else
    {
        // The operation has not been cancelled yet.
        // So proceed doing something very slow.

        // Calculate and report the progress as a 

        // percentage of the total task
        int percentComplete = (currentValue * 100) / 
            maxValue;worker.ReportProgress(percentComplete);
    
        // Return the result
        return result;
    }
}

See, no hassles on whether we are on the right thread or not. We simply attach a few handlers to the events exposed by the component, and focus on our task. With this, we have setup a clean implementation to invoke a method asynchronously, get frequent updates as to its progress, support for canceling the method, and finally handling the completion of the method.

Summing it up

Let’s revisit the steps involved in implementing an asynchronous operation using the BackgroundWorker component.

  1. Drag and drop a BackgroundWorker component from the Toolbox into the form.
  2. Add handlers to the DoWork, ProgressChanged and RunWorkerCompleted events as necessary.
  3. Have your long-running process check periodically the status of the BackgroundWorker.CancellationPending property.
  4. Let your process calculate the percentage of the task completed, and invoke the BackgroundWorker.ReportProgress method passing the percentage.
  5. Handle the BackgroundWorker.RunWorkerCompleted event to check whether this process was completed successfully, cancelled, or whether an exception had been thrown.

Cheers!

About the Code

The accompanying code is a Visual Studio .NET 2005 Beta solution for a Windows Forms project which demonstrates the Background Worker component with progress notification and cancellation support.

History

  • 11.Nov.2004: Just got published.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Rakesh Rajan
Web Developer
India India
Rakesh Rajan is a Software Engineer from India working at Technopark, Trivandrum in Kerala. He is a Microsoft MVP and an MCSD (.NET) with a few other certifications, and had been working in .NET for the past 3 years. He graduated majoring in Computer Science during his memorable days at Ooty (a wonderful hill station in Southern India). You can find him posting at newgroups, writing articles, working on his own projects or taking some time off by listening to music by Enya or Yanni, or reading an Archer or Sheldon.
 
Find his online publications here.
 
Rakesh blogs at http://rakeshrajan.com/blog/ and maintains a site http://rakeshrajan.com/.
He used to blog at http://www.msmvps.com/rakeshrajan/.
 
Drop him a mail at rakeshrajan {at} mvps {dot} org.

Comments and Discussions

 
QuestionWhat if I need to update the UI with anything else than a percentage? Pinmemberronnylov10-Dec-07 5:55 
AnswerRe: What if I need to update the UI with anything else than a percentage? PinmemberVJSWAMI4U5-Mar-09 4:07 
GeneralReset CancellationPending Flag PinmemberSamer Abu Obaid16-Aug-07 0:57 
GeneralGreat Article, Thanks PinmemberBobbyChuck19-Jul-07 13:50 
GeneralRe: Great Article, Thanks PinmvpNishant Sivakumar19-Jul-07 14:12 
General2 or more Backgroundworkers PinmemberMohammad Riazi25-Mar-07 19:44 
GeneralException !!! Pinmemberakorolev1021-Jul-06 5:09 
m_BackgroundWorker.RunWorkerAsync(100);
 
This BackgroundWorker is currently busy and cannot run multiple tasks concurrently.
 

System.InvalidOperationException was unhandled
Message="This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."
Source="System"
StackTrace:
at System.ComponentModel.BackgroundWorker.RunWorkerAsync(Object argument)
at BackgroundWorkerClient.AsyncClient.OnStart(Object sender, EventArgs e) in C:\Home\New2\297\Net Code\123\BackgroundWorker with 2.0\BackgroundWorker with 2.0\AsyncClient.cs:line 163
at System.Windows.Forms.Control.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at BackgroundWorkerClient.AsyncClient.Main() in C:\Home\New2\297\Net Code\123\BackgroundWorker with 2.0\BackgroundWorker with 2.0\AsyncClient.cs:line 101
at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

 
test

GeneralRe: Exception !!! PinmemberJudah Himango14-May-07 9:05 
QuestionHow TO Dataset Async Update Pinmemberakorolev1030-Jun-06 0:16 
GeneralNice, should repost and change Whidbey refs. to vs2k5 PinmemberJohn Cardinal31-Jan-06 8:21 
GeneralThread priority PinmemberGustav Pettersson3-Nov-05 0:27 
GeneralRe: BackgroundWorker for .NET v1.1 PinmemberRakesh Rajan19-Nov-04 5:47 
GeneralBackgroundWorker for .NET v1.1 PinmemberOskar Austegard19-Nov-04 3:48 
GeneralNicely described Pinmemberbenjymous11-Nov-04 1:31 
GeneralRe: Nicely described PinmemberRakesh Rajan11-Nov-04 1:53 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 11 Nov 2004
Article Copyright 2004 by Rakesh Rajan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid