Developer

Learning C#? Get Into The Easy Habit Of Writing Multi-Threaded Code For Better Performance

With even the most low-end desktops sporting two cores and mature operating systems that are very capable of handling multiple threads, there’s no excuse not to take explicit advantage of threads in your code. Today we’re going to look at adding asynchronous operation to a C# application using .NET’s BackgroundWorker class.

Image: stock.xchng

Note: This article assumes the reader has working knowledge of the .NET Framework and is comfortable with C#.

You can go a long time without writing multi-threaded code, but eventually, you’ll want to throw some tasks onto another thread. Multi-threading isn’t just about improving the performance of your application, it’s about delivering a responsive user experience. Say your application needs to load in a large XML file, or access a resource over the internet. Both of these activities can be handled asynchronously so as not to freeze the main interface or make the application appear to have locked up.

Some classes in the .NET Framework natively support multi-threading, or more accurately, asynchronous operation. WebClient, for example, provides methods to download and upload data synchronously and asynchronously, so all you have to do is add an event handler for the appropriate completion event and respond to the result.

On the other hand, if you use the XmlDocument class’ Load method to request an XML file via a URL, the program will wait for the request to complete before continuing execution. During that time, the user won’t be able to interact with the user interface and you won’t be able to update the UI to let the user know what’s going on.

When a class does not support asynchronous operation, we have to code up our own solution. Perhaps the easiest way to implement this functionality is to use the .NET Framework’s BackgroundWorker class, which can be found in the System.ComponentModel namespace.

Using BackgroundWorker

Let’s expand on the XmlDocument example and code up an asynchronous routine for it.

The first thing to do is to add a reference to System.ComponentModel and System.Xml, so at the top of the class file, type in the following lines:

using System.ComponentModel;
using System.Xml;

Now, create a static variable called “AsyncXmlLoader” of the BackgroundWorker class, as well as one for the XmlDocument class called “AsyncXmlDocument”:

private static BackgroundWorker AsyncXmlLoader;
private static AsyncXmlDocument;

You won’t be able to use this instance straight away — it needs to be initialised and the event handlers added. So, create a dedicated initialisation method or type in the following lines where your application starts:

AsyncXmlLoader = new BackgroundWorker();
AsyncXmlLoader.WorkerReportsProgress = true;

AsyncXmlLoader.ProgressChanged += AsyncXmlLoader_ProgressChanged;
AsyncXmlLoader.DoWork += AsyncXmlLoader_DoWork;
AsyncXmlLoader.RunWorkerCompleted += AsyncXmlLoader_RunWorkerCompleted;

What we’ve done here is initialise the AsyncXmlLoader variable, marked it so it can report its progress and then hooked into the three important events: reporting progress, performing the actual work and reporting completion.

Let’s fill out the DoWork method. It’s actually very straightforward:

static void AsyncXmlLoader_DoWork(object sender, DoWorkEventArgs e)
{
    AsyncXmlDocument = new XmlDocument();
    AsyncXmlDocument.Load("http://www.example.com/document.xml");
    AsyncXmlLoader.ReportProgress(100);
}

The call to ReportProgress is mostly superfluous, but I wanted to show how it works. You can pass an integer to represent how far along the DoWork method is, or, you can pass an object that represents the state, say a string with a message or a more complex structure or class. This is received by the ProgressChanged method. In this method, we can simply update a label or log, or provide a full-blown progress bar. XmlDocument unfortunately doesn’t support granular progress reporting, so you’ll have to make do with a basic status update instead.

Why don’t we just update the interface in the DoWork method? The problem is we can’t alter UI elements created in the main thread, instead, we have to release to that thread momentarily and place our update code in there. There are ways around this, but it’s best practice not to make cross-thread calls.

Finally, we can tell the program what to do once it’s finished loading the XML file in the RunWorkerCompleted method. This could include enabling or making visible hidden controls, updating them with the information from the XML file beforehand.

You might be tempted to add a try / catch around the XmlDocument.Load call in the DoWork method, but you don’t have to. If an error occurs, the DoWork method will abort and pass the error to the WorkerCompleted method, which you can check via the e.Error variable. You can then handle the exception and provide the user with the appropriate response.

Once you’re done, you can start the BackgroundWorker with the following method call:

AsyncXmlLoader.RunWorkerAsync();

There’s no need to recreate the BackgroundWorker if you want to load another XML file. All you have to do is fire up this method again.

Expanding On The Example

Another option is to inherit the XmlDocument class and override the Load method to build asynchronous operation directly into the new class. You’d have to provide events for completion and progress reporting, but you could just wrap these around those of the BackgroundWorker class.

Writing multi-threaded code is not that scary. If you get into the habit of doing it now, when you do advance to more complex applications, it’ll be second nature. The .NET Framework provides an easy way to build multi-threading into your application with BackgroundWorker, however, the newer versions of the API provide the more advanced Tasks library. Don’t worry though, there’s nothing wrong with using BackgroundWorker, especially if you’re constrained to the .NET Framework 2.0.