Maintaining a Responsive UI

by Jason Clark of Wintellect

I would venture a guess that nearly every Windows user has used an application that hangs when it performs lengthy operations. Applications that communicate with databases or do other network operations such as e-mail are particularly susceptible to this phenomenon. To be clear, most applications that behave this way are not actually hung; instead it is merely the user interface of the application that has become unresponsive. Of course, to the end-user and to the OS a frozen UI is hard to distinguish from a hung application. In this article we are going to look at how to address this problem in your Windows Forms Applications.

The solution to this problem is to never do a lengthy operation on the same thread that is managing your UI. A simple solution, in concept, this approach does come with a few idiosyncrasies. So here is the plan: in this article we will start by building an application that exhibits the problem, and then we will improve upon the code in two steps until we have a complete solution. So without further ado, let's go.

using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;

class App {
   public static void Main() {
      Application.Run(new ResponsiveForm());
   }
}

// Form-derived class
class ResponsiveForm : Form {

   // Couple of fields to hold references to controls
   TextBox textbox;
   Button button;

   // .ctor to set up the form with child controls
   public ResponsiveForm() {

      // Create a textbox
      this.textbox = new TextBox();      
      this.textbox.Size = new Size(100, 24);
      this.textbox.Location = new Point(10, 10);
      this.Controls.Add(this.textbox);

      // Create a button with event handler
      this.button = new Button();
      this.button.Text = "Do Processing...";
      this.button.Size = new Size(100, 24);
      this.button.Location = new Point(10, 40);
      this.button.Click += new EventHandler(this.OnButtonClick);
      this.Controls.Add(this.button);
   }

   // When the button clicks count up to the number in the textbox
   void OnButtonClick(Object sender, EventArgs args) {
      try {

         Int32 num = Int32.Parse(this.textbox.Text);
         // Call a method that does work that takes time
         this.DoLengthyOperation(num); 

      } catch (FormatException) {
         MessageBox.Show(this, 
            "Enter numeric text in TextBox", "Error");
      }
   }

   // Method does arbitrarily lengthy operation
   void DoLengthyOperation(Int32 num) {
      for (Int32 index = 0; index < num; index++) {
         Console.WriteLine(index); // WriteLine the count
         Thread.Sleep(100);
      }
   }
}

Figure 1 ResponsiveUIPart1.cs -- An Unresponse Application

The code in Figure 1 is a complete application that presents a UI with a button and a text box. The user enters a number into the text box, and clicks the button. The application processes for a length of time relative to the number entered into the text box. The processing is actually a loop that writes a value to the console, so to see the full effect you should build the application in Figure 1 as a console application.

Build Note To build all four stages of code in this article, unzip the code file into a directory. Then run the .build.bat file, or open the .sln file in the sln subdirectory, and build-all from Visual Studio .NET v1.1. Either approach will place four executables in the same directory as the source code. The executables are named ResponsiveUIPart1.exe — ResponsiveUIPart4.exe.

Try building and running the application in Figure 1. Type the number 200 into the text box and press the button. You will see that the application goes unresponsive for about twenty seconds. And yet if you watch the console window that accompanies the UI you can see that processing is still occurring. The application is actually doing exactly what it is designed to do. Unfortunately, it is designed to have an unresponsive UI while doing lengthy processing.

What is the Problem?

Ok, so our part-1 application is not responsive when the user kicks-off a lengthy operation; but why? The reason is this: Windows communicates UI events, such as mouse clicks, to applications via Window Messages. Messages must be actively retrieved and dispatched by application code in a fairly simple loop that has come to be known as a Message Pump. If for any reason the application stops pumping messages, even briefly, then the applications UI becomes unresponsive until the pump resumes.

Many programming environments, such as Visual Basic 6.0 and earlier, shield the programmer from the details of pumping messages, but they are actually true of every Windows program written since Windows v1.0. If the concept of pumping Window Messages is a new one to you, then probably two questions sprung to mind. First, where is the message pump in the program in Figure 1? Second, what is it about the program in Figure 1 that causes the pump to stop processing messages?

Let's tackle the first question. Source code line 8 in Figure 1, is the one and only line of code in Main and it reads like so:

Application.Run(new ResponsiveForm());

The Run method of the System.Windows.Forms.Application class in the class library implements a message pump. And when you create a Windows Forms-based application, your application lives its entirely life pumping messages inside of Application.Run.

Ok, so this is where our application pumps messages, but what about question 2? What, exactly, is it about our application that causes the message pump to stop? Well here it is. When the message pump dispatches a method, it does this using the same thread that is pumping messages. The process of dispatching a message involves calling code to handle the message. Long-story short: OnButtonClick is an example of a method that handles messages dispatched by the message pump, and until it returns, the pump is waiting on application code.

That is the problem in a nutshell; now let's start solving the problem.

The Solution Has Some Problems

The code in Figure 2 will remain responsive to the user, regardless of operations that it is performing. In fact, the problem is that the application is too responsive to the user. Build the code in Figure 2 and give it a run and see.

using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;

class App {
   public static void Main() {
      Application.Run(new ResponsiveForm());
   }
}

// Form-derived class
class ResponsiveForm : Form {

   // Couple of fields to hold references to controls
   TextBox textbox;
   Button button;

   // .ctor to set up the form with child controls
   public ResponsiveForm() {

      // Create a textbox
      this.textbox = new TextBox();      
      this.textbox.Size = new Size(100, 24);
      this.textbox.Location = new Point(10, 10);
      this.Controls.Add(this.textbox);

      // Create a button with event handler
      this.button = new Button();
      this.button.Text = "Do Processing...";
      this.button.Size = new Size(100, 24);
      this.button.Location = new Point(10, 40);
      this.button.Click += 
                 new EventHandler(this.OnButtonClick);
      this.Controls.Add(this.button);
   }

   // When the button clicks count up to the number 
   // in the textbox
   void OnButtonClick(Object sender, EventArgs args) {
      try {
 
         Int32 num = Int32.Parse(this.textbox.Text);
         // Call asynchronously method that does work 
         // that takes time
         WaitCallback doWork = 
            new WaitCallback(this.DoLengthyOperation);
         ThreadPool.QueueUserWorkItem(doWork, num);

      } catch (FormatException) {
         MessageBox.Show(this, 
            "Enter numeric text in TextBox", "Error");
      }
   }

   // Method does arbitrarily lengthy operation
   void DoLengthyOperation(Object param) {
      Int32 num = (Int32) param;
      for (Int32 index = 0; index < num; index++) {
         Console.WriteLine(index); // WriteLine the count
         Thread.Sleep(100);
      }
   }
}

Figure 2 ResponsiveUIPart2.cs -- An Response Application, With Problems

The lines in Figure 2 shown in red are the lines that were updated from the previous incarnation of the application. The gist is that the DoLenghtyOperation method is now being executed on a background thread from the thread pool. This leaves the UI thread free to return to the message pump to pump more messages for the application. However, this also has the side-effect of allowing the user to initiate multiple lengthy operations at once, which usually is not the preferred operation for the application. Try it with the code in Figure 2; run the application, enter 200 into the text box, and then press the button several times. You will see each operation working blindly alongside any other processing, each operation stepping on any others' toes all along.

Imagine that rather than count, the DoLengthyOperation method is doing a SQL query which can take up to a minute to process. It's great to keep the UI responsive; however it isn't great to let the user initiate more then one query. That would not benefit the user, and meanwhile the server then must serve multiple requests, all but one of which are unnecessary and will have their results ignored completely.

Again, we need a solution, and this time the solution comes in the form of disabling and re-enabling certain portions of the UI of the application before and after lengthy operations are performed. But there is a bit more to it then just that, so again, let's look at some changes to our application.

using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;

class App {
   public static void Main() {
      Application.Run(new ResponsiveForm());
   }
}

// Form-derived class
class ResponsiveForm : Form {

   // Couple of fields to hold references to controls
   TextBox textbox;
   Button button;

   // .ctor to set up the form with child controls
   public ResponsiveForm() {

      // Create a textbox
      this.textbox = new TextBox();      
      this.textbox.Size = new Size(100, 24);
      this.textbox.Location = new Point(10, 10);
      this.Controls.Add(this.textbox);

      // Create a button with event handler
      this.button = new Button();
      this.button.Text = "Do Processing...";
      this.button.Size = new Size(100, 24);
      this.button.Location = new Point(10, 40);
      this.button.Click += 
             new EventHandler(this.OnButtonClick);
      this.Controls.Add(this.button);
   }

   // When the button clicks count up to the number 
   // in the textbox
   void OnButtonClick(Object sender, EventArgs args) {
      try {
 
         Int32 num = Int32.Parse(this.textbox.Text);

         SetDoingLengthyOperation(true);

         // Call asynchronously method that does work 
         // that takes time
         WaitCallback doWork = 
            new WaitCallback(this.DoLengthyOperation);
         ThreadPool.QueueUserWorkItem(doWork, num);

      } catch (FormatException) {
         MessageBox.Show(this, 
            "Enter numeric text in TextBox", "Error");
      }
   }

   // Method does arbitrarily lengthy operation
   void DoLengthyOperation(Object param) {
      Int32 num = (Int32) param;
      for (Int32 index = 0; index < num; index++) {
         Console.WriteLine(index); // WriteLine the count
         Thread.Sleep(100);
      }      

      // Re-enable UI
      this.SetDoingLengthyOperation(false);
   }

   // Enables and disables UI, also makes sure it runs 
   // on UI thread
   void SetDoingLengthyOperation(Boolean working) {
      if (this.InvokeRequired) { // Make sure we run on UI thread
         // Create a delegate to self
         HelperDelegate setDoingLengthyOperation = 
            new HelperDelegate(this.SetDoingLengthyOperation);
         // Roll arguments in an Object array
         Object[] arguments = new Object[]{working};
         // "Recurse once, onto another thread"
         this.Invoke(setDoingLengthyOperation, arguments);
         return; // return;
      }

      // If this is executing then the call occured on the 
      // UI thread so we can freely access controls
      this.textbox.Enabled = !working;
      this.button.Enabled = !working;
   }

   delegate void HelperDelegate(Boolean working);
}

Figure 3 ResponsiveUIPart3.cs -- An Response Application, With Problems

The code in Figure 3 shown in red reflects changes to the part-2 incarnation. You would think that disabling the UI before starting the operation on the background thread, and then re-enabling the UI in the background thread would be simple. But alas, with Windows forms, the background thread should not be the thread to do the re-enabling, so this introduces a wrinkle.

Thou Shalt Not Do UI From Just Any Thread

Windows Forms are a managed API for doing UI, and a pretty good one at that. But still, Windows Forms are just a wrapper around existing Windows UI concepts, and for reasons related to the Windows messages and message pumps you should never call methods or properties on your GUI objects from just any thread. Here comes the rule.

Important Rule You should only access objects derived from System.Windows.Forms.Control (and that includes Form) from the thread that you used to create the objects.

Generally, this means that your main thread is the only thread you should use to enable and disable controls, populated lists, etc. This rule has three exceptions, which I will discuss in a moment, but first let's pay a little more attention to the rule.

The gist of the rule is that windows and controls are owned by threads. This means some operations will only work when performed from the owning thread. Additionally, the Windows Forms classes occasionally destroy controls and windows under the covers, only to rebuild them without the programmer or end-user ever being the wiser. But if code calls a method on a control from a background thread then this destruction and reconstruction process will occur on that background thread. This will have the effect of alienating the control from the message pump that is still running on the main UI thread. This is not a good thing.

The solution is to make sure that when your background processing has finished, you somehow let the main thread know to finishing work. The finishing work is often a few control-enablings, and perhaps the population of a list, table or textbox with a result from the background operation. And how does the background thread get this work to occur on the main thread? Remember those three exceptions to the rule I mentioned? The Invoke and BeginInvoke methods on Form (or the base class Control) are methods that are meant to be called from any arbitrary thread. In fact, the job of these methods is to call a method, through a delegate object, using the thread that pumps messages for the Control-derived object on which you are calling Invoke.

Exceptions to the Rule The Control.Invoke, Control.BeginInvoke and Control.InvokeRequred members are thread-safe and are meant to be accessed by any thread in your application.

If this all seems a little blurry, check out the SetDoingLengthyOperation method in Figure 3, to help clear things up. This method's job is to enable or disable pieces of the UI depending on whether a lengthy operation is just about to start, or has just finished. But notice also that the SetDoingLengthyOperation method also performs some upfront work to see if it needs to hop on over to the UI thread before it does its real job. The way that it does this is by calling the property InvokeRequired. If InvokeRequired is true, then you are processing on the wrong thread for doing UI, and you need to do your business via a call to Invoke on the Form or Control with which you are working. SetDoingLengthyOperation does just this.

The benefit of capturing the work of invoking across thread boundaries in the method itself is that now you can make calls to the method from any other code, running on any other thread, and the method knows just what to do if it needs to change threads before doing its work. And that's it; that is the last step in the process of making an application both responsive while still following the threading-related rules of Windows Forms objects.

Some Parting Thoughts

The techniques used in this article can be a little bit tough to grasp at first, but they are widely reusable techniques that can really improve the user experience with your applications. Building and running the code, as well as walking through the examples will help out in understanding the techniques.

There is certainly room for enhancement to the simple examples I show in this article. Two possible enhancements that come to mind right away are a progress meter and the ability to make operations cancelable. Both of these topics are weighty enough together to deserve another article. However in the code samples that I put together for this piece, I did make a fourth revision of the ResponsiveUI application that supports a simple cancel operation. Perhaps you will find it helpful in getting started with some more advanced responsive UI techniques.

Thread correctness is another consideration, which peripherally relates to cancellation. In general most applications aren't complex enough to warrant multiple threads running at once, and that is good. As soon as you are actively processing on multiple threads, then you have to do some form of thread-synchronization to keep things in tact. Part 2 of the sample code suffered from unrestrained multithreading. But our final revision did not.

Even though our Part 3 application is technically multi-threaded, in practice it is still runs its business logic single-threaded. Sure the UI thread will pump messages while the background thread is processing, but the UI thread, because of the disabled UI, refuses to do any business logic while the background processes. This is key to the application's success. You should try to stick as close to this design as you can while remaining responsive at all times, this will greatly simplify the amount of thread-synchronization logic that you have to build into your application.

Well, that should be sufficient food for thought for now. Well wait! One more extra: along with the C# samples parts 1 through 4; I've also included a part-4 version in Visual Basic .Net as well. So regardless of your preferred .NET language, you can dig through the sources and try this stuff out. I hope you've find it useful.

Have fun!

Download Source Code

Download source code: ResponsiveUI.zip - 12kb

About the Author...

Jason Clark has been banging code since he fell in love with computers way back in sixth grade. Since the early nineties, Jason professional life has been devoted to Windows development. His most recent full-time employment was with Microsoft, where he wrote security protocols for the Windows operating systems.

Jason now does software consulting and writes about a variety of topics ranging from developing secure software to writing software that runs on Microsoft's new .NET platform. Jason coauthored Programming Server-Side Applications for Microsoft Windows 2000, and he writes articles for Dr. Dobbs Journal, MSDN Magazine (formerly MSJ), Windows Developers Journal, and other leading developer magazines. Jason's commercial software credits include work he has performed for Microsoft, IBM, Sony, HP, and other companies, and involve everything from writing printer drivers to helping develop the Windows 2000 and Windows Server 2003 operating systems.

# # #



Comments

  • Another Source.

    Posted by ennrike on 04/06/2009 04:24pm

    This is a good article, however, the use of recursion make it complex to understand at the beginning, Check this other article, it is from MSDN: http://msdn.microsoft.com/en-us/magazine/cc300429.aspx

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With 81% of employees using their phones at work, companies have stopped asking: "Is corporate data leaking from personal devices?" and started asking: "How do we effectively prevent corporate data from leaking from personal devices?" The answer has not been simple. ZixOne raises the bar on BYOD security by not allowing email data to reside on the device. In addition, Zix allows employees to maintain complete control of their personal device, therefore satisfying privacy demands of valued employees and the …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds