Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Application Responsiveness


Platform Mitigations

The CLR, WPF framework, and Windows all mitigate these general problems to some degree. First, the CLR is responsible for ensuring that any time a GUI thread blocks, it does so in a GUI-friendly manner. It uses existing Win32 primitives under the covers—MsgWaitForMultipleHandles, CoWaitForMultipleTasks, and so forth—which are special functions that unblock either when the condition the thread has blocked for occurs, or when one of a few special UI events arrives at the message queue. In the latter case, the message loop runs to process a message. This ensures that, although a thread may have blocked, it wakes up just long enough to process a new GUI message. In the previous example, if any of event #6's time was wasted blocking (highly likely, unless it is entirely CPU bound), this would permit a dispatch of additional messages. While this might have resulted in repainting the screen, the shutdown request would still have to wait. It might be processed but first attempts an orderly shutdown, which requires that it wait for the current message to finish processing.

Also, WPF doesn't maintain a simple FIFO ordering for messages in the queue. Instead, it offers a prioritization mechanism to guarantee that urgent messages—a keystroke or attempt to close the window—are processed before other less urgent messages, such as a general-purpose application event, helping to maintain responsiveness. The system controls priorities in most cases, but when queuing work manually (via Dispatcher.Invoke and BeginInvoke), you pass in a DispatcherPriority value specifying the priority of the work. This feature would have helped slightly in the example we saw earlier, in Figure 2. Assuming events #6-#8 were not urgent priority, the window close event would have been processed at time 10 instead of 12, saving the user two-seconds-worth of frustration.

Finally, the foreground UI thread on Windows enjoys something called a "priority boost." This ensures that the UI gets more of the overall system time-slices, and that when a new event occurs at the message queue, the UI thread is given preferential treatment by the scheduler for the next time-slice. The result is that a more responsive system is maintained. This helps in situations where multiple applications are fighting for a processor, but unfortunately, if the code performs an explicit I/O operation or encounters page faulting, it can cause the UI thread to relinquish its time-slice voluntarily. Windows offers other innovations, like Superfetch in Windows Vista, that help eliminate page faulting of frequently utilized code and data.

Solutions

The first step to addressing this problem is to understand how UI events occur, and the general category of operations that wreak havoc. Next, understanding the performance characteristics of any code called from the UI thread is key. This not only requires an understanding of what methods can block, the data-intensiveness, and the range in execution time, but also what code paths lead to code executing on the UI thread. Determining this can be tricky. There is no standard mechanism in the documentation that tells you the performance characteristics of a method, whether it blocks, and if so, whether it does so in a GUI-friendly manner. Measurement is important.

Rather than issuing variable latency operations on the GUI thread, your application should delegate that work to occur on a separate thread, rendezvousing back with the UI thread at some later point; for instance, to update a window element with progress, or to initialize a control tree with data loaded from the network. This is as simple as using the CLR's ThreadPool, and manually invoking the Dispatcher's Invoke or BeginInvoke method to rendezvous, as in Example 2.

class MyWindow : Window {
  Button myButton = /*...*/;
  void myButton_Click(object sender, RoutedEventArgs e) {
    ThreadPool.QueueUserWorkItem(delegate {
       // Perform intensive operation
       myButton.Dispatcher.BeginInvoke(delegate {
          // Update UI element based on outcome of
          // the previous operation.
        });
    });
  }
}

Example 2: Using the ThreadPool to avoid blocking the UI thread.

While orchestrating this manually works fine, the System.ComponentModel.BackgroundWorker component encapsulates the code required to perform these transitions. Thanks to the AsyncOperationManager infrastructure used for many Component-derived types, this class works just as well with WPF as it does Windows Forms, firing events on the appropriate thread automatically. It has a DoWork event, responsible for executing the heavy work, which is automatically scheduled on the ThreadPool. It also offers ProgressChanged and RunWorkerCompleted events, letting you hook progress change and completion events, both of which occur on the UI thread, so that you may easily update visuals to notify the user of status.

It is also often important to support cancellation. It's nice to tell users what's going on, but if they still have to sit there watching a progress bar for 30 seconds while some network operation times out, they will likely become frustrated and kill the program. A responsive application should provide a way to cancel the operation if they decide they do not wish to wait for it.

The Dispatcher.BeginInvoke method returns a DispatcherOperation object with which you may cancel the operation via the Abort method. Provided that it hasn't been dispatched yet, this just deletes the target message from the UI thread's message queue. This does nothing about the fact that runaway operations continue to execute, however. To solve that, many component model .NET types provide a CancelAsync method. The BackgroundWorker class is one such type. System.Net.WebClient is another type, offering a DownloadXxxAsync method that performs the heavy network work— DNS resolution and data transfer—on a separate thread, firing back interesting events on the UI thread, supporting cancellation, and so on. Using it requires writing code to handle the various events, but is worth the effort for costly operations (say, more than three seconds in maximum duration).

Windows Vista also provides a general-purpose I/O cancellation feature, which can be used to cancel in-progress file I/O operations (see msdn.microsoft.com/library/en-us/dnlong/html/win32iocancellationapisv2.asp), but unfortunately, has no managed code support and does not yet work for network operations. It's worth keeping an eye on future releases. In the absence of a platform-wide feature like this, you must take care to write I/O code carefully, even when it's executing on a nonUI thread, taking note of options for timeouts and selecting reasonable values, and performing data operations incrementally. For example, instead of using the File.ReadAllLines API, consider using a FileStream, reading a line at a time, and checking for cancellation every so often. This also lets you indicate progress using some form of progress bar.

Conclusion

The types of problems I've described here are only becoming more prevalent in GUI applications. The increasing address space on 64-bit, memory sizes, and disk capacities on today's average desktop machine and focus on rich multimedia experiences mean that programs are operating on larger quantities of data than ever before. Users demand that of programs. Further, no application is an island—those variable latency network activities are becoming more and more common for all types of applications, not just those that were traditionally viewed as "network applications." Consider that Office 12 has deep integration with SharePoint Server, and that even Windows Vista is becoming more connected, with integration of RSS into core OS features. This trend will continue.

At the same time, the availability of multicore processors means we should take advantage of concurrency wherever possible. As it turns out, the requirements to do so are closely related to the requirements for maintaining responsive applications. But along with more parallelism comes the requirement to properly synchronize and coordinate activities across a process, involving the use of mutual-exclusion locks and events. This leads to additional blocking points and the possibility of deadlock, and must be managed carefully.

DDJ


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.