Everything I know about building a responsive Windows Forms application

The more I read about multi-threading, the less I feel I understand. Perhaps a good rule is don’t write multi-threaded code, similar to the first law of Distributed Objects. Fortunately, all I am going to talk about in this article is my experiences of making a responsive Windows Forms UI, using the BackgroundWorker and other techniques.

Full documentation on the BackgroundWorker can be found here, but the basic usage of the BackgroundWorker is to sign up to the DoWork eventhandler with the long-running task, and call the RunWorkerAsync method to kick the task off.

A naive example:

private void btn_Click(object sender, EventArgs e)
{
   BackgroundWorker backgroundWorker = new BackgroundWorker();
   backgroundWorker.DoWork+=delegate
   {
      for(int i = 0 ; i < 10; i++)
       {
           label1.Text = DateTime.Now.ToString();
           Thread.Sleep(1000);
        }
     };
     backgroundWorker.RunWorkerAsync();
 }
 

This will not work - we will get an InvalidOperationException at runtime - "Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on.". The issue being that attempting to write to the control label1 is not allowed due to the "legacy" issues that Windows leaves us. Incidentally, we get the same issue in WPF.

We need to make sure that we do not call back to the UI thread, except on the event handlers that Microsoft has kindly made for us, in this case ProgressChanged and RunWorkerCompleted.

A non-crashing example:

private void btn_Click(object sender, EventArgs e)
{
     BackgroundWorker backgroundWorker = new BackgroundWorker();
     backgroundWorker.DoWork += delegate
    {
       for(int i = 0; i < 10 ; i++)
        {
             backgroundWorker.ReportProgress(i, DateTime.Now.ToString());
             Thread.Sleep(1000);
        }
     };
     backgroundWorker.WorkerReportsProgress = true;
     backgroundWorker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args) 
      { 
         label1.Text = args.UserState.ToString(); 
      };
 
      backgroundWorker.RunWorkerCompleted += delegate
      {
          label1.Text = "Completed";
      };
      backgroundWorker.RunWorkerAsync();
    }
 

If you can't guarantee that your code that invokes a BackgroundWorker will be on the UI thread, you will need to test this with InvokeRequired which is a property on the base class Control, and if necessary call the method Invoke, which calls your UI update code on the main UI thread. Invoke is a fairly expensive operation, so I don't recommend just calling Invoke regardless of whether it is needed. An example of this:

backgroundWorker.ProgressChanged +=
delegate(object s, ProgressChangedEventArgs args)
{
     if(InvokeRequired)
     {
         Invoke(new Action<object>(UpdateLabel), args.UserState);
     }
     else
     {
         UpdateLabel(args.UserState);
     }
};

Using these handful of techniques, you should be able to build rock-solid, responsive User Interfaces, until you need to share data between threads that is, but that is a different issue alltogether. Are there any other techniques you use for responsiveness applications with Windows Forms?

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)
This entry was posted in UI, Windows Forms. Bookmark the permalink.