Thursday, May 14, 2009

BackgroundWorker In .NET

BackgroundWorker In .NET

I'm putting this here because I spent a lot more time visiting a lot more sites than I thought that I should to find out what I needed to invoke a thread using the BackgroundWorker class in .NET. In case you have the same need, here it is.

If a user interface function in .NET wants to perform some operation that is going to take a long time, you need to do something to make sure that the user interface still gets a chance to run. While Windows is a pre-emptive scheduler (meaning that you don't have to give up control to make sure that other tasks run), it appears that the main Windows message processing loop may not get a chance to run in compute intensive operations, preventing update of the user interface.

So, there's two ways around the problem:

1. Call Application.DoEvents periodically, to make sure that the Windows UI gets a chance to process message traffic. Ugly, especially if there is no obvious way to figure out how often to call this.

2. Use the BackgroundWorker class. (This is the preferred approach.)

BackgroundWorker class is a method for starting a thread, and receiving messages when that thread completes, or makes progress. Remember that even though the code is all present in one file (even in one class, if you choose), there are actually two separate threads that execute in parallel. The UI does the following sequence to set up a thread:

// Creates a background task control object.
BackgroundWorker backgroundTask = new BackgroundWorker();

// Adds an event handler that will start the background task. The
// backgroundTaskStart is part of the background thread.
backgroundTask.DoWork += DoWorkEventHandler(backgroundTaskStart);

// Adds an event handler that will be called when the background task
// completes. The backgroundTaskCompleted is part of the UI thread.
backgroundTask.RunWorkerCompleted += RunWorkerCompletedEventHandler(backgroundTaskCompleted);

// You need to set WorkerReportsProgress true if you want the UI thread to
// be informed when the background task updates the progress information.
backgroundTask.WorkerReportsProgress = true;

// Set up the event handler that gets called when the background task wants
// to report progress to the UI. Remember that backgroundTaskProgress is in
// the UI thread.
backgroundTask.ProgressChanged += ProgressChangedEventHandler(backgroundTaskProgress);

// Tell the backgroundTask object to pass a parameter to the background thread,
// and get it running. There has to be some way to pass more than one parameter
// to the background thread, but I found that it was simpler to put all the
// other information in class level members of the UI thread; the background task
// has access to this information.
backgroundTask.RunWorkerAsync(oneParameter);

// This method executes your background task, and returns whatever return code
// (if any) you want sent back to the UI task, stored in e.result.
private void backgroundTaskStart(Object sender, DoWorkEventArgs e)
{
e.Result = backgroundTask();
}

// This method is called when the background task completes. Because it is
// within the UI thread, it can do operation such as updating a status bar.
private void tableExporterCompleted(Object sender, RunWorkerCompletedEventArgs e)
{
// update status bar, re-enable buttons that you may have disabled
// before starting the background thread.
}

// Here's where I ran into some lack of information. Ordinarily, a background
// thread informs the UI of what percent progress it has made. I didn't want to
// pass a number, but a series of messages showing, for example, how many
// records had been processed out of the total number, or various stages
// along the way. So I had the background thread update a string, progressMsg,
// and the update event handler merely informs the UI that there is a fresh
// message waiting to be displayed in the status bar.
private string progressMsg;

// This method gets called by the background thread periodically, with a
/ string that I want displayed on the status bar.
private void progress(string msg)
{
progressMsg = msg;
// Ordinarily, the parameter passed to ReportProgress is a percent complete.
// In this case, it's a dummy; ReportProgress sends a message to the
// progress event handler in the UI thread.
backgroundTask.ReportProgress(0);
}

// The BackgroundWorker object, when it gets a ReportProgress invocation, sends
// the event that executes this method, which updates the UI with the string
// most recently stored in progressMsg by the background thread.
private void backgroundTaskProgress(Object sender, ProgressChangedEventArgs e)
{
// Update the status bar in the form.
logToStatusBar(progressMsg);
}

No comments:

Post a Comment