Keep your UI responsive with the BackgroundWorker

Standard
Share

I recently completed an application that made use of the WebClient object’s DownloadString method to obtain a JSON string. In my development environment retrieval of this data often took upwards of 30 seconds. If you’ve ever incorporated a time-consuming process in your application, you may have noticed that your user interface becomes unresponsive while the process is running.

Why does this happen? Because without special consideration, this time-consuming process is running on the same thread that handles updating of the UI. “How do I keep my UI responsive during a time-consuming process?”, you may ask. This can be easily accomplished through the use of the BackgroundWorker object. In this article, we’ll discuss the BackgroundWorker object, some of its available event handlers, and one of its methods.

In my recently completed application, I used the following code (in my application’s DataModel) to obtain a JSON string:

public string JSONString { get; set; }

private bool isInProgress = false;
public bool IsInProgress
{
    get
    { return isInProgress; }
    set
    {
        isInProgress = value;
        NotifyPropertyChanged("IsInProgress");
    }
}

internal void RetrieveDataFromURL()
{
    WebClient client = new WebClient();
    JSONString = client.DownloadString("http://www.example.com/json");
}

Since I was calling this code from my application window’s ContentRendered event, it appeared that my app would load and then immediately freeze while obtaining data from the web:

private DataModel dm = new DataModel();

public MainWindow()
{
    InitializeComponent();
    this.DataContext = dm;
}

private void MainWindow_ContentRendered(object sender, EventArgs e)
{
    dm.RetrieveDataFromURL();
}

Obviously an application with the appearance of being frozen upon startup is less than ideal. I wanted to allow my UI to remain responsive while the WebClient’s DownloadString method was running. I accomplished this by modifying my ContentRendered event handler, then adding a few methods to my DataModel class – specifically, “RunBackgroundWorker”, “BackgroundWorker_DoWork”, and “BackgroundWorker_WorkCompleted”.

First, the change to the ContentRendered event handler:

private void MainWindow_ContentRendered(object sender, EventArgs e)
{
    dm.RunBackgroundWorker();
}

Then, the three additions to my DataModel class:

internal void RunBackgroundWorker()
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(BackgroundWorker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker_WorkCompleted);
    worker.RunWorkerAsync();
}

internal void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // This is the method that now contains the time-consuming task
    if( IsInProgress )
    {
        return;
    }
    else
    {
        IsInProgress = true;
        RetrieveDataFromURL();
    }
}

internal void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    IsInProgress = false;
}

Let’s break this down piece by piece. First we introduce the RunBackgroundWorker method. It creates an instance of the BackgroundWorker object called worker:

BackgroundWorker worker = new BackgroundWorker();

Then we assign to worker the method that it should run when it is told to work and the method that it should run when it completes its work:

worker.DoWork += new DoWorkEventHandler(BackgroundWorker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker_WorkCompleted);

Finally, we tell worker to do this work asynchronously:

worker.RunWorkerAsync();

In the BackgroundWorker_DoWork event handler, we first check the boolean IsInProgress. We’ll use this boolean to determine if the worker object is already in progress. If it is set to true, we return control to the calling method:

if ( IsInProgress )
{
    return;
}

Otherwise, we set our IsInProgress boolean to true, then place a call to the original time-consuming process to instantiate the WebClient object and download our JSON string:

else
{
    IsInProgress = true;
    RetrieveDataFromURL();
}

The last method we need to analyze is the BackgroundWorker_RunWorkerCompleted event handler. Ours is quite simple, as all we’re doing is setting the IsInProgress boolean back to false:

IsInProgress = false;

That’s all there is to it. Instead of calling our time-consuming method directly from our window’s ContentRendered event handler, we’re passing it off to a background worker. This allows our UI to remain responsive while the background worker whiles away in a separate thread.

On a side note, you can also bind your UI controls’ IsEnabled properties to the IsInProgress boolean on the DataModel. The use of:

NotifyPropertyChanged("IsInProgress");

indicates that our DataModel implements the INotifyPropertyChanged interface (although I don’t show this code). As such, any controls with an IsEnabled property bound to this boolean (with a boolean inverter) will automatically disable when the background worker is in progress and automatically re-enable when the background worker completes its work.

P.S. While not covered in this article, the BackgroundWorker object supports cancellation (see WorkerSupportsCancellation property) and progress reporting (see WorkerReportsProgress property).

UPDATE
As it turns out, the use of WebClient in this example is a terrible idea. Why? Simply put, WebClient already offers asynchronous download abilities. See my post on this for more information.

2 thoughts on “Keep your UI responsive with the BackgroundWorker

Leave a Reply

Your email address will not be published. Required fields are marked *