In UI programming, it’s easy to unwittingly make inefficient use of computing resources by blocking or executing long running tasks on the main thread rather than dispatching work to worker threads when advantageous.  It is good practice to dispatch any non-trivial, non-UI task to a thread pool (or another resource manager) so that the UI thread can handle new messages that it receives.  If the UI thread is unable to process new messages, the program will appear to be unresponsive to the user.   Ideally, the UI thread will be able to wait on UI messages almost all the time because it is not engaged in complex processing. 

For example, one common mistake is to perform I/O in a UI callback.  If the I/O is blocking, the user will experience unresponsiveness.  This problem is especially worrisome when the I/O is slow.  Consider the case of a network server waiting on client communication.  The main thread could be blocked for many seconds, or even forever!

The Concurrency Visualizer can be helpful in diagnosing these performance problems.  When profiling a UI application, we would expect to see mostly “UI Processing” segments on the main thread.  These segments are displayed in taupe on the timeline.  For example, we would expect a picture that looks like this:

 

clip_image002

Notice there are many small execution (green) segments.  These are tiny bits of work that the main thread is able to execute efficiently because they are very short.  We can zoom in to easily see what’s happening if the main thread is executing or is blocking for longer than we expect:

image

In this case, there is an executing segment on the main thread.  Clicking on the largest segment shows us the nearest sample profile and its executing call stack.  Notice the WndProc with my method “ShortRunningWork”.  Let’s assume that the entire collection of green and red segments currently visible on the timeline is processing being done in the ‘ShortRunningWork’ method within the WndProc.   So the total time spent  doing non-UI processing, or equivalently, the total time  in which the UI is unresponsive is about 20 milliseconds.  How long is too long to be unresponsive?  Under 50ms is certainly considered ‘instantaneous’, and in fact users do not notice delays of up to 195ms [1].  While short running work can run on the UI thread, it’s a good idea to get in the habit of dispatching work to worker threads.  (Side note:  in fact, it’s not worth dispatching work if it’s too short, because then the overhead involved in maintaining a worker thread and context switching between them may take more time than your work itself.) 

Consider the worst case scenario, my work takes an indefinitely long time and the program appears to hang:

clip_image006

Profiling this application with the Concurrency Visualizer will show something like this:

clip_image008

The Execution Profile Report shows that 98% of the executing callstacks are in my WndProc’s “LongRunningWork” function.  It should be clear from this result and the picture that the LongRunningWork function should be executed on a separate worker thread.  This example is a native Win32 application, so there are a number of options in deciding how to offload this “LongRunningWork”.  The simplest option would be to use the CreateThread API to create a new thread with a pointer to the LongRunningWork function, but it might be a better idea to create a small group of reusable threads, or to use a library which does thread pooling or task scheduling for you.  If you’re writing a managed application, you have a number of managed thread pooling and managed task scheduling options. 

Since there are a plethora of options for multithreading your code, I’ll leave the implementation of this up to the reader.  As an exercise, create a GUI project in Visual Studio (using whatever libraries and languages you feel most comfortable with—WPF, WinForms, Win32, etc.).  Write some long-running code that will get executed on the UI thread.  If you don’t feel like writing any setup code, you can use this simple WPF snippet to get started.

MainWindow.xaml
<
Window x:Class="WpfPlayground.MainWindow"
        xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
        xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
        Title="WPF example that has really long running work." Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
               <
RowDefinition Height="*" />
            <
RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Grid.Row="0">Program output:</Label>
        <TextBlock Name="OutputTextBlock" Grid.Row="1" />
        <Button Name="StartLongRunningTaskButton" Grid.Row="2" Click="StartLongRunningTaskButton_Click">Start a Long Running Task</Button>
    </Grid>
</Window>

MainWindow.xaml.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

 

    private void StartLongRunningTaskButton_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 10000000; i++)
            if (IsPerfect(i))
                OutputTextBlock.Text += string.Format("{0} is a perfect number!\n", i);
    }

 

    // A positive integer is "perfect" if it is the sum of all it's positive integer factors

    private bool IsPerfect(int n)
    {
        int sum = 1;
        for (int i = 2; i < n; i++)
            if (n % i == 0)
                sum += i;
        return (n == sum);
    }

}


Profile the program using the Concurrency Visualizer.  You should see a picture similar to the profile of the native Win32 example above or a managed app like this:

clip_image010 

 

Here comes the fun part:  figure out a way to safely dispatch your work to a worker thread.  Think about how you can return the result of your worker thread to the UI thread.  If you’re working with the WPF code sample above, I set it up so you’ll be forced to think about this as soon as you dispatch the long running work to another thread.

 

[1] Dabrowski, J. R. and Munson, E. V. 2001. Is 100 Milliseconds Too Fast?. In CHI '01 Extended Abstracts on Human Factors in Computing Systems (Seattle, Washington, March 31 - April 05, 2001). CHI '01. ACM, New York, NY, 317-318. DOI= http://doi.acm.org/10.1145/634067.634255

Matthew Jacobs – Parallel Computing Platform