Tags

, ,

[New version here]

The not so secret recipe for a responsive User Interface is to farm out as much work as possible to a different thread. The easiest approach in the .NET world is to create a BackgroundWorker and a couple of delegates. It is a very simple model and the pseudo code is:

public partial class Form1
{
    BackgroundWorker _worker = new BackgroundWorker();

    private void LoadFile(string filter)
    {
        _worker.DoWork += (sender, e) =>
            { // read a large file from disk };    

        _worker.RunWorkerCompleted += (sender, e) =>
            {
                txtFileContents.Text = e.Result.ToString();
                lblStatus.Text = "Loaded";
            };
        lblStatus.Text = "Loading...";
        _worker.RunWorkerAsync(theFilePath);
    }
}

It is hard to make it any simpler. The only caveats are that you have to know about delegates and you have to grok the fact that your code does not run in order. In fact the first line to run is “txtStatus.Text = “Loading…”;” followed by the “_worker.RunWorkerAsync()” which then kicks off the DoWork block followed by the RunWorkerCompleted block.

The simplicity of this approach covers most scenarios but there are two disadvantages: you have to have one BackgroundWorker per concurrent piece of code and if you have several arguments you need to pass to the DoWork delegate you need to pack them manually. This can add up to a considerable amount of repeated code in complex UIs. To remedy the problem I came up with two classes that wrap the BackgroundWorker and remove the disadvantages. They also use a fluent interface or internal DSL to hopefully make the process more explicit. The bad news is that you still have to know about delegates. The translation of the example would be:

public partial class Form1
{
    private void LoadFile(string filter)
    {
        lblStatus.Text = "Loading...";

        WorkerPool.Next
          .AddArguments(theFilePath)
          .DoWork( e => { // read a large file }; )
          .WhenFinished( e =>
            {
                txtFileContents.Text = e.Result.ToString();
                lblStatus.Text = "Loaded";
            });
    }
}

The first advantage of this approach is that you don’t need to declare any BackgroundWorker. It still exists but it is in the WorkerPool class. The second advantage is the AddArguments method which accepts any number of arguments packs them into a list and passes it on to the DoWork delegate. Then there is the order of execution which matches the order of coding and, finally, there are several overloads for DoWork and WhenFinished simplifying the signatures of the delegates. The argument classes (the e parameters above) are of the exact instances that the BackgroundWorker generates so all the original information is there.

The disadvantage of this method is that you have to clean up after yourself. The BackgroundWorker is a component that when sited on a form or control is disposed by them. Since I am creating instances inside the WorkerPool class they have to be disposed of at some point. From my tests so far I haven’t found any problems and I will report back any I come across as I intend to use this library throughout the whole application I am building. Having said this, the WorkerPool recycles BackgroundWorkers and you should not have to clear it (WorkerPool.Clear()) at any time unless you are creating and cancelling large numbers of threads.

I will continue the description of WorkerPool in future posts covering thread cancelling, progress report and error handling. Meanwhile have fun with it and report back any bugs or improvements.

The code, in C# can be downloaded from here. This code requires .NET 3.5 but it can possibly run with .NET 2.0 if you replace the two or three Linq code lines.

[Use the code in this article instead].

Advertisements