Threading in WPF

Summary

Dispatcher

WPF applications start their lives with two threads: one for rendering and another for the UI. The rendering thread runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code. The UI thread queues work items inside an object called a Dispatcher. The Dispatcher selects work items on a priority basis and runs each one to completion. Every UI thread must have at least one Dispatcher and each Dispatcher can execute work items in exactly one thread.

Note: Do not mix DispatcherObject and the Dispatcher. DispatcherObject is a base class that all WPF classes inherit from. DispatcherObject represents a WPF object that has a Dispatcher, and this implies that DispatcherObject-derived classes have thread-affinity A Dispatcher is a class that maintains a prioritized queue of work items for the thread that it is associated with. A DispatcherObject-derived class can obtained its Dispatcher by calling the static Dispatcher.CurrentDispatcher property.

The trick to building responsive applications is to maximize the Dispatcher throughput by keeping the work items small. But what if the application logic involves a complex calculation or needs to run an operation on some remote server? The answer is to handle the complex or remote operation in a separate thread, leaving the UI thread free to tend to items in the Dispatcher queue. When the big operation completes, it can report its result back to the UI thread for display.

Windows only allows UI elements to be accessed by the thread that created them. This means that a background thread in charge of some long running task canít update a textbox when itís done. Windows does this to ensure the integrity of UI components. WPF has a built in mutual exclusion mechanism to enforce this restriction. Just about every class in WPF descends from DependencyObject. At construction, a DependencyObject stores a reference to the Dispatcher linked to the currently running thread. During program execution, a DependencyObject can call its public VerifyAccess method which examines the Dispatcher associated with the current thread and compares it to the Dispatcher reference stored during construction. If they donít match, VerifyAccess throws an exception. VerifyAccess is intended to be called at the beginning of every method belonging to a DependencyObject.

Given that only one thread can modify the UI, how do background threads interact with the user? A background thread can ask the UI thread to perform an operation on its behalf. It does this by registering a work item with the Dispatcher of the UI thread. The Dispatcher class provides two methods for registering work items: Invoke and BeginInvoke. Both methods schedule a delegate for execution. Invoke is a synchronous call Ė that is, it doesnít return until the UI thread actually finishes executing the delegate. BeginInvoke is asynchronous and returns immediately.

The Dispatcher orders the elements in its queue by priority. There are ten levels that may be specified when adding an element to the Dispatcher queue. These priorities are maintained in the DispatcherPriority enumeration.

Using the Dispatcher

Highly responsive applications were typically implemented in Windows Forms using a background thread to perform length operations. On completion, the background thread communicated with the UI thread via Control.InvokerRequired and Control.Invoke/Control.BeginInvoke. With WPF, highly responsive applications can be implemented in variety of ways by using the Dispatcher and/or background threads.

Dispatcher With No Background Threads

Recall that a Dispatcher is a class that maintains a prioritized queue of work items for the thread that it is associated with. The emphasis here in on prioritized queue. Using Dispatcher.BeginInvoke you can schedule length operations in the same queue that UI events are drawn from. Using queue priorities, you can, for example, invoke the lengthy operations when the UI thread is idle. This is how Microsoft Word performs spell checks: checking is done in the background using the idle time of the UI thread.

public partial class Window1 : Window
{
    public delegate void NextPrimeDelegate();

    //Current number to check
    private long num = 3;
    private bool continueCalculating = false;

    public Window1() : base()
    {
        InitializeComponent();
    }

    // Click handler for a button to start/stop prime number generation
    public void StartOrStop(object sender, EventArgs e)
    {
        if (continueCalculating)
        {
            continueCalculating = false;
            startStopButton.Content = "Resume";
        }
        else
        {
            continueCalculating = true;
            startStopButton.Content = "Stop";

            // Note how prime number generator is queued to the dispather
            startStopButton.Dispatcher.BeginInvoke( DispatcherPriority.Normal,
                                                    new NextPrimeDelegate(CheckNextNumber));
        }
    }

    public void CheckNextNumber()
    {
        // Perform prime-number checking
        ...

        if (continueCalculating)
        {
            // Note how generating the next prime number is queued to the
            // dispather with SystemIdle priority
            startStopButton.Dispatcher.BeginInvoke( DispatcherPriority.SystemIdle,
                                                    new NextPrimeDelegate(this.CheckNextNumber));
        }
    }

    private bool NotAPrime = false;
}

Dispatcher With Background Threads

In this example a timer is used to invoke a refresh method every n seconds. Because the timerís method is invoked on a thread from the thread pool. Dispatcher is used within the timerís method to check that the invoking thread is the main thread before updating any GUI. If the invoking thread is not the GUI thread (i.e., it is the timerís thread), the call is re-invoked on the UI thread using Dispatcher.Invoke:

private void InitRefreshTimer()
{
    // Get refresh value
    double dRefreshInterval = Convert.ToDouble(ConfigurationManager.AppSettings["RefreshInterval"]);
    _timer.Interval = dRefreshInterval * 1000;
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    _timer.Start();
}

void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    Click_RefreshStatus(sender, null);
}

private delegate void delRefreshStatus(object sender, RoutedEventArgs args);
private void Click_RefreshStatus(object sender, RoutedEventArgs args)
{
    try
    {
        // Is Click_RefreshStatus being called from the main UI thread or
        // from the timer's thread?
        if (!btnRefresh.Dispatcher.CheckAccess())
        {
            // Called from timer's thread. Need to re-invoke on the UI main thread
            btnRefresh.Dispatcher.Invoke(DispatcherPriority.Normal,
                                         new delRefreshStatus(this.Click_RefreshStatus), sender,
                                         new object[] { args });
            return;
        }
        else
        {
       
            // Add logic to refresh status
            ...
        }
    }
    catch (Exception ex)
    {
        ...
    }
}

Multiple Windows and Multiple Threads

In this approach, each new window can run in its own thread. For example, when the user clicks the ďnew windowĒ button, we launch a copy of our window in a separate thread. This way, long running or blocking operations in one of the windows wonít lock all the other windows. The following code illustrates:

private void Button_NewWindowHandler(object sender, RoutedEventArgs e)
{
    // Create and start a new thread
    Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
}

// Thread function
private void ThreadStartingPoint()
{
    // Create a new window.
    Window1 win = new Window1();
    win.Show();

    // WPF automatically creates a new Dispatcher to manage our new thread. All we have to
    // do to make our window functional is ask this Dispatcher to start running
    System.Windows.Threading.Dispatcher.Run();
}