.NET

Practical Multithreading for Client Apps

Jason Clark

Code download available at:NET0401.exe(118 KB)

Contents

Threading Background
Why Use More Than One Thread?
Threading and the Windows UI
The Managed Thread Pool
Thread Synchronization at its Simplest
Details—Implementing a Cancel Button
More Details—a Word on Timers
A Peek into the Future

Writing applications that use multiple threads is often considered an advanced programming task, prone to errors. In this month's column, I'll focus on a practical application of multithreading in Windows® Forms applications with some real benefits, while attempting to keep things simple. My goal is to present multithreading in an approachable way that addresses a very common need: writing applications with a user interface that remain responsive to the user.

Multithreading is often associated with server-side software, scalability, and performance technology. However, with the Microsoft® .NET Framework, many server-side applications are hosted in the ASP.NET architecture. As such, these applications are logically single threaded because IIS and ASP.NET perform much or all of the multithreading in an ASP.NET Web Form or Web Service application. You can usually ignore threading altogether for ASP.NET applications. This is one reason why the threading features of the .NET Framework are oriented towards client-side uses such as assuring a responsive user interface while performing lengthy operations.

Threading Background

Threads execute code. They are implemented by the OS and are an abstraction of the CPU itself. While many systems have only a single CPU, threads are a way of dividing a fast CPU's processing ability among a number of operations to make them seem simultaneous. Even a system that has multiple CPUs will typically have more threads running than it has processors.

In Windows-based applications, each process has at least one thread which is capable of executing machine-language instructions. Once all of the threads in a process have terminated, the process itself and all of its resources are cleaned up by Windows.

Many applications are designed as single-threaded applications, meaning that the process that implements the application never has more than one thread executing even though other processes may be active in the system at the same time. Normally, a process does not have to concern itself with the threads executing in other processes in the system.

All of the threads in a single process, however, share not only the virtual address space of the process but also many other process-level resources, such as file and window handles. Because of the shared nature of the resources in a single process, threads typically have to be concerned with what other threads in the same process are doing. Thread synchronization is the art of keeping multiple threads in a single process from stepping on each other's toes. This is what makes multithreading difficult.

The best approach is to use multiple threads only as needed, keeping things as simple as possible. Also, try to avoid situations that will require thread synchronization. In this column, I will show you how to do these things for common client applications.

Why Use More Than One Thread?

Many single-threaded client applications already exist, and many more are being written daily. In many cases, single-threaded behavior is sufficient. However, for certain applications, adding a little asynchronous behavior can improve the user's experience. A typical database front end makes a good example.

Database queries can take varying amounts of time to complete. In a single-threaded application, these queries can block the application's ability to process window messages, causing the application's UI to freeze. The solution, which I will cover in more detail, is to continue processing messages from the OS in one thread while doing lengthy work in another. Keeping your application's UI responsive, even while it's busy working behind the scenes, is one great reason to make use of a second thread in your code.

Let's look first at a single-threaded GUI application that performs a lengthy operation. Then we will spruce up the application with additional threads.

The code in Figure 1 is the complete source for an application written in C#. It creates a form on which there is a textbox and a button. If you enter a number into the textbox and then press the button, the application will process for that number of seconds, beeping once per second to represent background processing. In addition to the code in Figure 1, the complete download for this column can be found at the link at the top of this article. Download or type in the code in Figure 1, compile it, and run it before reading on. (Before compiling, right-click your project in Visual Studio® .NET and add a reference to the Microsoft Visual Basic® .NET runtime.) When you try to run the SingleThreadedForm.cs application in Figure 1, you'll immediately see some problems.

Figure 1 SingleThreadedForm.cs

using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using Microsoft.VisualBasic; class App { // Application entry point public static void Main() { // Run a Windows Forms message loop Application.Run(new SingleThreadedForm()); } } // A Form-derived type class SingleThreadedForm : Form { // Constructor method public SingleThreadedForm() { // Create a text box text.Location = new Point(10, 10); text.Size = new Size(50, 20); Controls.Add(text); // Create a button button.Text = "Beep"; button.Size = new Size(50, 20); button.Location = new Point(80, 10); // Register Click event handler button.Click += new EventHandler(OnClick); Controls.Add(button); } // Method called by the button's Click event void OnClick(Object sender, EventArgs args) { // Get an int from a string Int32 count = 0; try { count = Int32.Parse(text.Text); } catch (FormatException) {} // Count to that number Count(count); } // Method beeps once per second void Count(Int32 seconds) { for (Int32 index = 0; index < seconds; index++) { Interaction.Beep(); Thread.Sleep(1000); } } // Some private fields by which to reference controls Button button = new Button(); TextBox text = new TextBox(); }

On your first test run, enter 20 into the textbox, and press the button. You will see that the application's UI becomes entirely unresponsive. You can't click the button or edit the textbox, the application cannot be closed gracefully, and if you obscure and subsequently expose a portion of the window, it will not repaint itself (see Figure 2). The application has effectively locked up for 20 seconds, and yet it continues to beep away, proving that it has not really crashed. This simple application illustrates the problem with single-threaded GUI applications.

Figure 2 No Repaint

Figure 2** No Repaint **

This unresponsive UI is the first problem I'll solve with multithreading, but first I'll explain what causes this phenomenon.

Threading and the Windows UI

The Windows Forms class library is built around a Win32® API known as User32. User32 implements GUI elements such as windows, menus, buttons, and the like. All windows and controls implemented by User32 use an event-driven messaging infrastructure.

In brief, here is how it works. Things that happen to windows, such as mouse clicks, coordinate changes, size changes, and paint requests, are called events. Events in the User32 API model are represented by window messages. Each window has a function, called a window procedure or WndProc, which is implemented in the application. WndProc functions are responsible for processing the window messages for the window.

But the WndProc is not magically called by the system. Instead, the application must actively get window messages from the system by calling an API function called GetMessage. These same messages are dispatched to their destination window's WndProc method by an application call to the DispatchMethod API function. Applications do the receiving and dispatching of window messages in a loop commonly called a message pump or message loop. The thread owns all of the windows for which it pumps messages, and the WndProc functions are called by this same thread.

Now back to the Windows Forms classes. Windows Forms probably abstracts 95 percent of the messaging infrastructure of User32 away from the application. Instead of WndProc functions, Windows Forms applications define event handlers and virtual function overrides to handle the various system events that relate to a form (window) or control. However, the message pump must still be run, and it is implemented in the Application.Run method in the Windows Forms API.

The code in Figure 1 seems only to call Application.Run and then exit. However, it's less apparent that the application's main thread lives its entire life in a single call to Application.Run pumping messages, which results in calls into the various event handlers for the rest of the application. When the button on the form is clicked, the OnClick method in Figure 1 is called by the main thread; this is the same thread that is responsible for pumping messages in the Application.Run method.

This explains why the UI becomes unresponsive when a lengthy operation is occurring. If a lengthy operation (such as a database query) occurs in an event handler the thread is tied up, which keeps it from pumping messages. The inability to pump messages translates to the window or form's inability to respond to resizes, repaint itself, handle clicks, or do anything in response to user interaction.

In the following section I will modify the sample code shown in Figure 1 in order to use the common language runtime's (CLR) thread pool for lengthy operations so that the main thread will remain available for message pumping.

The Managed Thread Pool

The CLR maintains a thread pool for every managed process, meaning that if your application needs something to happen asynchronously of your main thread, you can easily borrow a thread from the thread pool to do the work. Once your work is finished the thread is returned to the pool for later use. Let's look at the form example, modified to use the thread pool.

Note the lines of FlawedMultiThreadedForm.cs in Figure 3 that are shown in red; these are the only code changes necessary to turn the single-threaded code in Figure 1 into a multithreaded application. If you build the code found in Figure 3 and set it running for 20 seconds, you will see that the UI remains responsive while processing the 20-beep request. This responsive UI is the compelling reason to multithread in client applications.

Figure 3 FlawedMultiThreadedForm.cs

using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using Microsoft.VisualBasic; class App { // Application entry point public static void Main() { // Run a Windows Forms message loop Application.Run(new FlawedMultiThreadedForm()); } } // A Form-derived type class FlawedMultiThreadedForm : Form { // Constructor method public FlawedMultiThreadedForm() { // Create a text box text.Location = new Point(10, 10); text.Size = new Size(50, 20); Controls.Add(text); // Create a button button.Text = "Beep"; button.Size = new Size(50, 20); button.Location = new Point(80, 10); // Register Click event handler button.Click += new EventHandler(OnClick); Controls.Add(button); } // Method called by the button's Click event void OnClick(Object sender, EventArgs args) { // Get an int from a string Int32 count = 0; try { count = Int32.Parse(text.Text); } catch (FormatException) {} // Count to that number <span xmlns="https://www.w3.org/1999/xhtml">WaitCallback async = new WaitCallback(Count); ThreadPool.QueueUserWorkItem(async, count); } // Async method beeps once per second void Count(Object param) { Int32 seconds = (Int32) param; for (Int32 index = 0; index < seconds; index++) { Interaction.Beep(); Thread.Sleep(1000); } }</span> // Some private fields by which to reference controls Button button = new Button(); TextBox text = new TextBox(); }

However, by making the changes shown in Figure 3, a new problem is introduced (as indicated by the figure's name); now the user can fire off multiple lengthy simultaneous beep operations. In many real apps this would qualify as one thread stepping on another's toes. To fix this requires thread synchronization. I will address this, but first let's take a closer look at the CLR's thread pool.

The System.Threading.ThreadPool class in the class library provides an API to the CLR's thread pool. The ThreadPool type cannot be instantiated and consists of static members. The most important methods on the ThreadPool type are the two overloads of ThreadPool.QueueUserWorkItem. Both of these methods let you specify a method that you would like to be called back by a thread from the thread pool. You specify the method using an instance of the WaitCallback delegate type defined in the class library. One of the overloads allows you to specify a parameter to the asynchronous method; this is the version used in Figure 3.

The following two lines of code are used to create a delegate instance representing a method named Count followed by a call to queue up a callback to the method from the thread pool:

WaitCallback async = new WaitCallback(Count); ThreadPool.QueueUserWorkItem(async, count);

Both of the ThreadPool.QueueUserWorkItem methods add your specified asynchronous callback method to a queue and then return immediately. Meanwhile, the thread pool monitors the queue and begins dequeuing the methods and calling them using a thread or threads from the pool. This is the primary use of the CLR's thread pool.

The CLR's thread pool is also used by other APIs in the system. For example, the System.Threading.Timer object queues up callbacks on the thread pool at timed intervals. The ThreadPool.RegisterWaitForSingleObject method queues up callbacks from the thread pool in response to a kernel synchronization object being signaled. And finally, callbacks performed by the various asynchronous methods in the class library are performed using the CLR's thread pool.

In general, an application that needs to use multiple threads only for simple asynchronous operations should use the thread pool exclusively. This is recommended over the manual creation of thread objects. Calls to ThreadPool.QueueUserWorkItem are simple to perform and make better use of system resources than the repeated creation of manual threads.

Thread Synchronization at its Simplest

Earlier in this column I defined thread synchronization as the art of keeping multiple threads from stepping on each other's toes. The problem with the FlawedMultiThreadedForm.cs application shown in Figure 3 is that after the user initiates a lengthy beep operation by clicking the button, they can continue to click the button and initiate more beep operations. If, instead of a beep, the lengthy operation is a database request or the manipulation of a data structure in the process' memory, you certainly would not want two or more threads doing the same work at exactly the same time. At best this is a waste of system resources, and at worst it can cause data corruption.

The simplest solution to this problem is to disable the portion of the user interface that initiates any operation that is incompatible with the lengthy operation currently running on a thread from the pool. Then, when the operation has finished, the thread pool thread can communicate back to the main thread, letting it know that it is finished and that it is safe to reenable the disabled portions of the user interface.

The easy part of this task is the disabling of UI elements like buttons; communication between the two threads is slightly more difficult. I will show you how to do both of these things in a moment. But first, let me point out that all thread synchronization is performed using some form of inter-thread communication—a means of communication from one thread to another. Later I will discuss an object type known as an AutoResetEvent that is used solely for inter-thread communication.

Now let's look at some code that adds thread synchronization to the FlawedMultiThreadedForm.cs application shown in Figure 3. Again, the CorrectMultiThreadedForm.cs shown in Figure 4 is only a minor modification of its predecessor, with the changes shown in red. If you run the application you will see that the UI is disabled (but doesn't hang) while a beep operation is running, then reenabled when finished. This time there are enough code changes that I will run through each of them.

Figure 4 CorrectMultiThreadedForm.cs

using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using Microsoft.VisualBasic; class App { // Application entry point public static void Main() { // Run a Windows Forms message loop Application.Run(new CorrectMultiThreadedForm()); } } // A Form-derived type class CorrectMultiThreadedForm : Form{ // Constructor method public CorrectMultiThreadedForm() { // Create a textbox text.Location = new Point(10, 10); text.Size = new Size(50, 20); Controls.Add(text); // Create a button button.Text = "Beep"; button.Size = new Size(50, 20); button.Location = new Point(80, 10); // Register Click event handler button.Click += new EventHandler(OnClick); Controls.Add(button); <span xmlns="https://www.w3.org/1999/xhtml">// Cache a delegate for repeated reuse enableControls = new BooleanCallback(EnableControls);</span> } // Method called by the button's Click event void OnClick(Object sender, EventArgs args) { // Get an int from a string Int32 count = 0; try { count = Int32.Parse(text.Text); } catch (FormatException) {} // Count to that number <span xmlns="https://www.w3.org/1999/xhtml">EnableControls(false);</span> WaitCallback async = new WaitCallback(Count); ThreadPool.QueueUserWorkItem(async, count); } // Async method beeps once per second void Count(Object param) { Int32 seconds = (Int32) param; for (Int32 index = 0; index < seconds; index++) { Interaction.Beep(); Thread.Sleep(1000); } <span xmlns="https://www.w3.org/1999/xhtml">Invoke(enableControls, new Object[]{true});</span> } <span xmlns="https://www.w3.org/1999/xhtml">void EnableControls(Boolean enable) { button.Enabled = enable; text.Enabled = enable; } // A delegate type and matching field delegate void BooleanCallback(Boolean enable); BooleanCallback enableControls;</span> // Some private fields by which to reference controls Button button = new Button(); TextBox text = new TextBox(); }

Near the end of Figure 4 is a new method named EnableControls which enables or disables the textbox and button controls on the form. Earlier in Figure 4 I added a call to EnableControls to disable the controls on the form immediately before queuing up a work item to perform the background beeping. At this point, half of the thread synchronization job is done because the user is unable to initiate a conflicting background operation due to the disabled UI. Near the end of Figure 4 you'll find the definition of a delegate type named BooleanCallback whose signature is compatible with the EnableControls method. Just before that definition, a delegate field named enableControls is defined (note the case), which refers to the EnableControls method for this form. This delegate field is assigned earlier in the code.

You'll also see a callback from the main thread that owns and pumps messages for the form and its controls. This call is made to the EnableControls method with a parameter of true to reenable the controls. This is done by calling the Invoke method on the Form object from the background thread once it has finished its lengthy beep operation. The code passes the delegate which refers to EnableControls to Invoke, along with an Object array for the method parameters. The Invoke method is a very flexible form of inter-thread communication specific to forms and controls in the Windows Forms classes. In this example, Invoke is used to tell the main GUI thread to reenable the controls on the form by calling the EnableControls method.

The changes made to CorrectMultiThreadedForm.cs in Figure 4 implement the solution I suggested earlier—disabling the portion of the UI that initiates the operation that you don't want running while the beep operation is executing. Then tell the main thread to reenable the disabled portions when the operation has finished. The call to Invoke is unique, and it deserves special attention.

The Invoke method is defined on the System.Windows.Forms. Control type, making the method available to all derived controls in the class library including the Form type. The purpose of the Invoke method is to marshal a call from any thread to the thread that implements the message pump for the control or form.

When accessing Control-derived classes, including the Form class, you must do so from the thread which pumps the control's messages. This happens naturally in single-threaded applications. But if you begin to use threads from the thread pool, it is important that you avoid calling methods and properties on your UI objects from the background thread. Instead, you must access the Control-derived type indirectly using its Invoke method. Invoke is one of the very few methods on controls that is safe to call from any thread because it's implemented using the Win32 PostMessage API.

Inter-thread communication using Control.Invoke is a bit complicated. But once you are comfortable with this process, you have all of the tools necessary to achieve your goal in using multiple threads in client applications. The remainder of this column covers some extra details, but the CorrectMultiThreadedForm.cs application shown in Figure 4 is a complete solution for remaining responsive to the user while performing arbitrarily lengthy operations. Although most of the user interface is disabled, the user can still reposition and resize the window, as well as close the application. However, the user can't abuse the asynchronous behavior of the application. These small details go a long way in terms of a user's confidence in your application.

It is worth noting that my first thread-synchronized solution is synchronized without making use of any traditional threading constructs such as mutexes or semaphores. Instead, I used the more common UI notion of disabled controls.

Details—Implementing a Cancel Button

Sometimes you want to provide your users with a way to cancel your lengthy operations. All you need is some way for your main thread to communicate to a background thread that its operation is no longer needed and it can halt. The System.Threading namespace provides a class for this, AutoResetEvent.

AutoResetEvent is a very simple mechanism for inter-thread communication. An AutoResetEvent object can be in one of two states: signaled or non-signaled. When you create an instance of AutoResetEvent, you decide its initial state using a constructor parameter. Then threads that are aware of the object can communicate with each other by checking or modifying the AutoResetEvent's state via its Set and Reset methods.

In a way the AutoResetEvent acts like a Boolean, but it offers features that make it more appropriate for inter-thread communication. One such example is the ability for a thread to efficiently wait until an AutoResetEvent's state changes from non-signaled to signaled. This is accomplished by calling the WaitOne method on the object. Any thread that calls WaitOne on a non-signaled AutoResetEvent object will block efficiently until some other thread signals the object. Using a Boolean variable, the waiting thread would have to inefficiently poll the variable in a loop. Typically there is no need to un-signal an AutoResetEvent object using Reset because the object is automatically reset to the non-signaled state as soon as any other thread notices that the object is signaled.

Now you need a way for the background thread to test the state of an AutoResetEvent object without blocking, and you'll have all the tools necessary to implement thread cancellation. To do this, call the overloaded form of WaitOne that takes a timeout value and indicate a timeout of zero milliseconds. This way WaitOne returns immediately, whether the AutoResetEvent object is signaled or not. If the return value is true, then the object is signaled; otherwise it is returned because of the timeout.

Let's bring it all together to implement a cancel feature. If you want to implement a cancel button on a form that performs lengthy operations on background threads, follow these steps:

  1. Add a field of type AutoResetEvent to your form.
  2. Instantiate an AutoResetEvent object in the non-signaled state by passing false as the object's constructor parameter. Then store the object's reference in the field on your form. You will use this object for the life of the form in order to communicate cancellation of background operations to background threads.
  3. Add a cancel button to your form.
  4. In the cancel button's Click event handler, signal the AutoResetEvent object by calling its Set method.
  5. Meanwhile, in your background thread's logic periodically call WaitOne on the AutoResetEvent object to check if the user has canceled:
if(cancelEvent.WaitOne(0, false)){ // cancel operation }
  1. You should remember to use a timeout parameter of zero milliseconds in order to avoid an unnecessary pause in the background thread's operation.
  2. If the user has canceled the operation, the AutoResetEvent will have been signaled by the main thread. Your background thread is alerted to this when it gets the true return value of WaitOne and ceases its operations. Meanwhile the event is automatically reset to the non-signaled state by the background thread's call to WaitOne.

To see an example of a form that can cancel its lengthy operations look for the CancelableForm.cs file that's available for download. The code is a complete app that differs only slightly from the CorrectMultiThreadedForm.cs app shown in Figure 4.

Note that the CancelableForm.cs sample file also employs a more advanced use of Control.Invoke, where the EnableControls method is designed to recall itself if it is being called from the wrong thread. It does this check before touching any methods or properties of the GUI objects on the form. This makes EnableControls safe to call directly from any thread, and effectively hides the complexity of the Invoke call in the method's implementation. Doing this can make for a more maintainable application overall. Note also that in this example EnableControls uses Control.BeginInvoke, which is the asynchronous version of Control.Invoke.

You might have noticed that the cancellation logic relies on the background thread's ability to periodically check for cancellation by calling WaitOne. But what if the lengthy background operation in question can't be canceled? What if the background operation is a single method call, like DataAdapter.Fill, that takes a long time? Sometimes there is a solution, though not always.

If your lengthy operation is fundamentally uncancelable, you may be able to get away with a pseudo-cancel where you let the operation finish, but do not act on the results of the operation in your application. This is not technically canceling the operation and does tie up a thread pool thread for the duration of the operation, but is an acceptable compromise for certain cases. If you implement a solution like this, you should reenable your disabled UI elements directly from your cancel button's event handler, rather than rely on the still-tied-up background thread to Invoke a call to enable your controls. It is also important that your background operation be designed to test to see if it has been canceled when it does return, so that it does not continue to act on the results of the now-canceled operation.

This is a more advanced approach to cancellation of lengthy operations that only works in certain cases. For example, a pseudo-cancellation of a database query is one thing, but pseudo-canceling a database update, deletion, or insert is a stickier operation. Operations that have persistent results or feedback-related operations, such as sounds and graphics, are not as easily pseudo-canceled using this approach because the effects of the operation remain obvious to the user after cancellation.

More Details—a Word on Timers

It is not uncommon for an application to need a timer to fire every so often to do some recurring task. For example, if your application showed the current time in a form's status bar, you might update the time every five seconds. The System.Threading namespace includes a multithreaded timer class called Timer.

When you create an instance of the Timer class, you indicate a period for the timer callback in milliseconds, and you also pass the object a delegate that it uses to call you back each time the timer period elapses. The callback occurs on a thread pool thread. In fact, what really happens each time the timer period is up is that a work item is queued to the thread pool; typically, the call is made right away, but if the thread pool is busy the callback may occur at a later point in time.

If you are considering making your application multithreaded, then you might consider using the Timer class for your timing needs. However, if your application uses Windows Forms, and you are not necessarily looking for multithreaded behavior, there is another timer class defined in the System.Windows.Forms namespace, also called Timer.

The System.Windows.Forms.Timer has a clear benefit over its multithreaded counterpart: since it is not multithreaded, it does not call you back on another thread, but rather uses your main thread which is pumping window messages for your application. In fact, the System.Windows.Forms.Timer class is implemented using a window message in the system called WM_TIMER. This way you do not have to worry about thread synchronization or inter-thread communication of any kind in your event handler for a System.Windows.Forms.Timer event.

For Windows Forms applications, a good rule of thumb is to use the System.Windows.Forms.Timer class unless you specifically need the callback to occur on a thread pool thread. Since this requirement is rare, keep things simple and use the System.Windows.Forms.Timer as the rule, even when other parts of your application are multithreaded.

A Peek into the Future

Microsoft recently demonstrated a forthcoming GUI API, code-named "Avalon," featured in Charles Petzold's article in this issue of MSDN Magazine (see page 70). User interface elements in the Avalon framework are not tied to a particular thread; instead each user interface element is associated with a single logical thread context implemented in the UIContext class. You might not be surprised to find out, though, that the UIContext class includes an Invoke method, as well as its sister BeginInvoke method, which serve logically identical purposes to the methods with the same name on the Control class in Windows Forms.

Send your questions and comments for Jason to  dot-net@microsoft.com.

Jason Clark provides training and consulting for Microsoft and Wintellect (https://www.wintellect.com) and is a former developer on the Windows NT and Windows 2000 Server team. He is the coauthor of Programming Server-side Applications for Microsoft Windows 2000 (Microsoft Press, 2000). You can get in touch with Jason at JClark@Wintellect.com.