Creating a Background Worker in a Windows Forms application

I was recently tasked with creating a small Windows Forms application for a client that allowed images to be captured for applicants. The workflow was simple, a WCF service provided a list of applicants waiting to have photos taken from which the user would select one. The applicants details would be confirmed and a photo taken. The image and some meta data would be sent back to the server via another WCF service operation.

The catch? The user (and the people queueing to have photos!) doesn't want to hang around while the image uploads via the WCF service. The solution? Store the meta data and a link to the image in a local SQL Server Compact Edition database from where a background worker thread can pick it up and do the upload, meanwhile the user takes another photo or two on the UI thread. The SQL CE database means that if the background worker doesnt upload an image before the application closes, it can pick it up and upload it the next time the application is running. We already had a Mindscape Lightspeed ORM licence for the WCF services on this project, so I used Lightspeed over SQL CE! Very cool, more on this in another post.

The code looked like this;

public partial class MainForm : Form, IMainForm
{
   private BackgroundWorker _backgroundWorker;
   ...
   public MainForm(IBackgroundUploader backgroundUploader)
   {
      ...
      _backgroundWorker = new BackgroundWorker();      
      _backgroundWorker.DoWork
         += backgroundUploader.ProcessQueueOngoing;
      _backgroundWorker.WorkerSupportsCancellation = true;
      _backgroundWorker.RunWorkerCompleted
         += backgroundWorker_RunWorkerCompleted;
      _backgroundWorker.RunWorkerAsync();   
   }
}

First I added a BackgroundWorker to the form which I initialise in the constructor and wire the DoWork event to the ProcessQueueOngoing handler on the BackgroundUploader object that got injected in. I then set the worker to support cancellation and add a handler to the RunWorkerCompleted event before firing off Asyncronous processing via the call to RunWorkerAsync.

public class BackgroundUploader : IBackgroundUploader
{
    ...
    public void ProcessQueueOngoing(object sender, DoWorkEventArgs e)
    {
       BackgroundWorker thread = (BackgroundWorker)sender;
       while (!thread.CancellationPending)
       {              
          //check the sql ce datastore to see if we have any
          //images waiting to be uploaded, if we do - upload
          //the first one
          ...
          Thread.Sleep(Configuration.UploadImageWorkerFrequency);
       }
       //if we have a pending cancellation,
       //set the event args property and return
       e.Cancel = true;
       return;
    }
}

The Background uploader is just a plain old C# class. The ProcessQueueOngoing method takes an event sender and a DoWorkEventArgs parameter. This is the method that is called when the DoWork event is fired due to us wiring it up above. The sender is the BackgroundWorker, to allow for the BackgroundWorker to be cancelled we have a while loop that will break when the CancellationPending bit is set on the background worker. Within that loop we check for any images that need uploading, upload them one at a time then sleep for an amount of time pulled from configuration.

When our application closes we want to gracefully end the background worker, allowing it to complete any pending updates. We do this during the MainForm's FormClosing event by calling CancelAsync on the background worker.

private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
     //tell the background worker to cancel its business.
     //it will finish a current operation if one exists.
     _backgroundWorker.CancelAsync();
     while(_backgroundWorker.IsBusy)
     {
        //call appliaction.DoEvents to Marshall the async IsBusy property update to the ui thread
        //while we wait for the background worker to finish
        Application.DoEvents();
     }
}

Because the background worker's DoWork delegate has returned, the IsBusy property will be set to false. For the UI thread to be aware of this however we need to call Application.DoEvents(); We do this in a while loop so as soon as the BackgroundWorker is no longer busy, the application exits.

Finally we need to handle any errors that might occur, log them and degrade gracefully. This is especially important as our background worker is talking to a SQLCE database and WCF services which may not be available for any reason. Any exception thrown in a BackgroundWorker will by design not bubble up to the UI thread, we need to explicitly listen for it.

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
      if (e.Error != null)
      {
         throw new BackgroundUploaderException("An error occured with the background uploader process",
         e.Error);
      }
}

We do this by subscribing to the BackgroundWorkers RunWorkerCompleted event, which will fire with an Exception in the RunWorkerCompletedEventArgs Error property if an exception is thrown. I have wrapped this in a custom exception type and re-thrown to be handled by the global exception handler and logged (exception handling is a whole seperate blog post and more, perhaps I will write about it in the future).

Thats it! Hopefully this helps anyone trying to do background processing in a Windows Forms application.

Tags: , , , , ,

Comments

trackback
DotNetKicks.com
3/28/2010 2:40:58 AM Permalink

Creating a Background Worker in a Windows Forms application

You've been kicked (a good thing) - Trackback from DotNetKicks.com

trackback
instantiate
6/25/2010 7:10:26 AM Permalink

The adapter pattern: abstracting camera hardware

The adapter pattern: abstracting camera hardware

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading