Appendix C. Asynchronous and Multithreaded WPF Programming

If you like to write applications that annoy your users, a good way to do this is to make the user interface stop responding to input from time to time. For extra frustration, you can compound the problem by not giving any visible indication that work might be progressing, leaving the user to wonder whether the application is busy or has simply crashed. Because you’ll get this behavior by default if you don’t take certain steps to maintain responsiveness, you can stop reading now. Unless, that is, you’d prefer not to annoy your users.

Unfortunately, it’s all too easy to write your application in such a way that it becomes unresponsive when it performs time-consuming work such as accessing a server over a network or reading files off disk. In Windows, all messages regarding user input for a particular window are delivered to the same thread. In general, this is a good thing, because it means your code has to deal with input events only one at a time, and does not have to worry about thread safety. The downside is that the application can respond to input only if this thread is available to process it.

Many APIs are synchronous—they do not return until they have completed the work you asked them to perform. (Such APIs are said to block.) If you write a handler for a button’s Click event that makes a synchronous API call to retrieve data from a database, that call will not return until the database returns the data. The thread cannot do anything else until that synchronous call returns, so the application will be unable to respond to any other user input for the duration of the call.

Even if you avoid synchronous APIs, you could still cause sluggishness simply through slow code. Code risks being slow if it performs either CPU-intensive or memory-intensive work. Slow CPU-intensive work is fairly uncommon—computers are fast enough these days that you need to find a considerable amount of work for processing to seem anything less than instantaneous, and only a handful of applications do this. However, excessive memory use is much more common, and it can have a drastic effect on speed, particularly once paging to disk occurs. If the OS has to load a page off disk back into memory, the amount of time this takes is long enough to execute tens of millions of instructions. This has to happen only a couple of times before it adds up to a perceptible delay. Whether code is slow due to memory or CPU usage, running slow code on the same thread that handles user input will make the UI unresponsive.

There are two ways to solve this problem. One is to use asynchronous APIs. Some parts of the .NET Framework offer asynchronous invocation, where the API call returns immediately without waiting for the work to complete. For example, instead of using the Stream class’s blocking Read method, you could call the nonblocking BeginRead, passing in a callback to be notified when the read operation completes. Alternatively, you can use multithreaded programming—if you execute code on some thread other than the thread that handles input, it doesn’t matter whether this other thread executes slow code or calls synchronous APIs, because the input handling thread is free to respond to other user input.

Tip

Multithreaded programming in WPF works in the same way as in any other .NET application. Because this appendix deals only with multithreading issues specific to WPF applications, we won’t be showing any of the general-purpose .NET threading techniques. For more general information on .NET’s multithreading facilities, consult the “Managed Threading” topic in the SDK documentation at http://msdn2.microsoft.com/library/3e8s7xdd.aspx (http://tinysells.com/80).

Using asynchronous APIs often results in the use of multiple threads. Although you might not explicitly create any new threads, you may be notified of the completion of some asynchronous operation on a different thread from the one that started the work. So, regardless of whether you choose to use asynchronous APIs or multithreading to keep your application responsive, an understanding of WPF’s threading model will be necessary.

The WPF Threading Model

Many WPF objects have thread affinity, meaning that they belong to a particular thread. Your code must always use a WPF user interface element on the same thread that created it. It is illegal to attempt to use any WPF element from any other thread.

Tip

If you are familiar with Windows Forms, you will be used to this threading model. If you are familiar with COM (the Component Object Model), you will recognize this as resembling the Single Threaded Apartment (STA) model.

WPF uses this model for various reasons. One is simplicity—the model is straightforward and does not introduce any complications for applications that have no need for multiple threads. This simplicity also makes it fairly straightforward for WPF to detect when you have broken the rules so that it can alert you to the problem with an exception. There are also performance benefits: thread affinity avoids locking, which is usually required with multithreaded models, and locks add both complexity and performance overhead.

Another important reason for using a single-threaded model is to support interop with Win32, which also has thread affinity requirements. (It is not quite as strict, in that it is possible to perform many operations from the “wrong” thread. However, such operations are often handled very differently than the same operations performed on the right thread, and numerous pitfalls are associated with cross-thread window usage.) By adopting a strict thread affinity model, you can mix WPF, Windows Forms, and Win32 user interface elements freely within a single application.

DispatcherObject

You might be wondering how you can be sure which types have thread affinity and which don’t. Although all user interface elements have this requirement, it does not apply to every single type you use in a WPF application. For example, built-in types not specific to WPF, such as Int32, do not care which thread you use them on, as long as you use them from only one thread at a time.

But what about types that are specific to WPF but are not user interface elements, such as Brush, Color, and Geometry? How are we to tell which types have thread affinity requirements? The answer is to look at the base class. WPF types with thread affinity derive from the DispatcherObject base class. Brush and Geometry both derive from DispatcherObject, so usually you can use them only on the thread that created them.[128] Color does not, and therefore you can use it on a different thread from the one on which it was created.

Tip

The DispatcherObject class defines a few members, all of which are exempt from the thread affinity rule—you can use them from any thread. Only the functionality added by classes that derive from DispatcherObject is subject to thread affinity.

DispatcherObject provides a couple of methods that let you check whether you are on the right thread for the object: CheckAccess and VerifyAccess. CheckAccess returns true if you are on the correct thread, false otherwise. VerifyAccess is intended for when you think you are already on the right thread, and it would be indicative of a problem in the program if you were not. It throws an exception if you are on the wrong thread. Example C-1 shows a method that uses this to ensure that it has been called on the right thread.

Example C-1. Use of VerifyAccess
public void Frobnicate(FrobLevel fl) {
    // Ensure we're on the UI thread
    VerifyAccess(  );

    ...
}

Many WPF types call VerifyAccess when you use them. They do not do this for every single public API, because such comprehensive checking would impose a significant performance overhead. However, it checks in enough places that you are unlikely to get very far on the wrong thread before the problem becomes apparent.

If your application causes multiple threads to be created, either through explicit thread creation or implicitly through the use of asynchronous APIs, you should avoid touching any user interface objects on those threads. If you need to update the user interface as a result of work done on a different thread, you must use the dispatcher to get back onto the UI thread.

The Dispatcher

Each thread that creates user interface objects needs a Dispatcher object. This effectively owns the thread, running a loop that dispatches input messages to the appropriate handlers. (It performs a similar role to a message pump in Win32.) As well as handling input, the dispatcher enables us to get calls directed through to the right thread.

Tip

The Dispatcher class lives in the System.Windows.Threading namespace along with all other WPF-specific threading classes, including DispatcherObject.

Obtaining a Dispatcher

Recall that all WPF objects with thread affinity derive from the DispatcherObject base class. This class defines a Dispatcher property, which returns the Dispatcher object for the thread to which the object belongs.

You can also retrieve the Dispatcher for the current thread by using the Dispatcher.CurrentDispatcher static property.

Getting Onto the Right Thread with a Dispatcher

If you need to update the user interface after doing some work on a worker thread, you must make sure the update is done on the UI thread. The Dispatcher provides methods that let you invoke the code of your choice on the dispatcher’s thread.

You can use either Invoke or BeginInvoke. Both of these accept any delegate and an optional list of parameters. They both invoke the delegate’s target method on the dispatcher’s thread, regardless of which thread you call them from. Invoke does not return until the method has been executed, whereas BeginInvoke queues the request to invoke the method, but returns straight away without waiting for the method to run.

Invoke can be simpler to understand, because you can be certain of the order in which things happen. However, it can lead to subtle problems—by making a worker thread wait for the UI thread, there is a risk of deadlock. The worker thread may be in possession of locks or other resources that the UI thread is waiting for, and each will wait for the other indefinitely, causing the application to freeze. BeginInvoke avoids this risk, but adds the complexity that the order of events is less predictable.

Example C-2 shows the use of the dispatcher’s BeginInvoke method. This is a typical way of structuring code that is not running on the UI thread, but which needs to do something to the UI. In this case, the code sets the background color of the window. The RunsOnWorkerThread method in this example runs on a worker thread. (The mechanism by which that worker thread was created is not shown here, because the techniques used for creating worker threads in WPF applications are exactly the same as those used in any other .NET application.)

Example C-2. Using Dispatcher.BeginInvoke
partial class MyWindow : Window {
    ...
    public delegate void MyDelegateType(  );

    void RunsOnWorkerThread(  ){

        Color bgColor = CalculateBgColor(  );
        MyDelegateType methodForUiThread = delegate {
            this.Background = new SolidColorBrush(bgColor);
        };
        this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, methodForUiThread);
    }

    ...
}

We’re using the C# anonymous delegate syntax here. You don’t have to use this—you could just put the code in a separate method. However, anonymous delegates are often particularly convenient in this kind of scenario, because you can use any of the variables that are in scope in the containing method. In this case, we are setting the bgColor variable in the containing method, and then using that value in the nested anonymous method that will run on the UI thread. This sharing of lexical scope across two methods makes moving from one thread to another relatively painless.

The first parameter passed to BeginInvoke indicates the priority with which we would like the message to be handled. The Dispatcher does not operate a strict “first in first out” policy, because some messages need to be handled with higher priority than others. For example, suppose two work items are queued up with the dispatcher, where one is a message representing keyboard input and the other is a timer event that will poll some remote service for status. The remote polling is likely to take a while to complete, so delaying the poll a little won’t have any visible effect. However, even fairly small delays in processing user input tend to make an application feel unresponsive, so you would normally want the key press to be handled before background tasks such as polling. The dispatcher therefore handles messages according to their specified priority to allow those that are sensitive to latency, such as input messages, to be handled ahead of less urgent tasks.

The Normal priority level is relatively high—it runs ahead of input processing and even rendering. For quick operations, this is not a problem, but in some cases you may want the work to run as a BackgroundWorker operation—something that will run only when there is nothing more important to do. For this kind of processing, use either the ApplicationIdle or the SystemIdle priority level. ApplicationIdle will not process the message until the application has nothing else to do. SystemIdle considers activity across the whole machine, and processes the message only when a CPU would otherwise be idle.

The second parameter to BeginInvoke is the delegate. The Dispatcher will invoke this at some point in the future on the dispatcher thread. If we had used a delegate type that required parameters to be passed to the target function, we would have used one of the overloads of BeginInvoke that accepts extra parameters, as Example C-3 shows.

Example C-3. Passing parameters with BeginInvoke
delegate void UsesColor(Color c);
void SetBackgroundColor(Color c) {
    this.Background = new SolidColorBrush(c);
}

void RunsOnWorkerThread(  ) {
    UsesColor methodForUiThread = SetBackgroundColor;
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, methodForUiThread,
                                Colors.Blue);
}

Here we have defined a custom delegate type called UsesColor. It requires its target function to take a single parameter of type Color. The delegate was defined to match the signature of SetBackgroundColor, the method we want to call. This method sets the window background color, so it needs to run on the UI thread. We’re assuming that the RunsOnWorkerThread method isn’t on the right thread, so it uses the Dispatcher.BeginInvoke method to call SetBackgroundColor on the correct thread.

However, there are a couple of differences between Example C-2 and Example C-3. One is cosmetic—we are no longer using the C# anonymous delegate syntax. The other is that we are now passing an extra parameter to BeginInvoke. You can pass as many extra parameters as you like—one of the BeginInvoke overloads accepts a variable length argument list. All of these parameters will be passed into the target function on the dispatcher thread.

If you are familiar with the .NET asynchronous pattern, you might be wondering whether there is an EndInvoke method. Typically, any call to a BeginXxx method has to be matched with a corresponding EndXxx call. But the Dispatcher does not use the standard asynchronous pattern. BeginInvoke has no corresponding EndInvoke method, nor does it provide a way of passing in a completion callback function to BeginInvoke as you would expect to see with a normal implementation of the .NET asynchronous pattern. However, it is possible to discover when an operation is executed by using the DispatcherOperation class. This class also supports cancellation, which is not available in the standard asynchronous pattern.

DispatcherOperation

The Dispatcher.BeginInvoke method returns a DispatcherOperation object. This represents the work item sent to the dispatcher. You can use it to determine the current status of the operation. Its Status property will be one of the values from the DispatcherOperationStatus enumeration, shown in Table C-1.

Table C-1. DispatcherOperationStatus values

Value

Meaning

Pending

The dispatcher has not yet called the method.

Executing

The method is currently executing on the dispatcher thread.

Completed

The method has finished executing.

Aborted

The operation was aborted.

You will see the Aborted status only if you cancel the operation. You can cancel an operation by calling the DispatcherOperation.Abort method. As long as the operation has not already started, this removes it from the dispatcher’s queue. This method returns true if the operation was cancelled, and false if it had already started by the time you called abort.

You can wait for the operation to complete by calling the Wait method. This blocks the worker thread until the UI thread has executed the method. (This carries the same risk of deadlock as Invoke.) Alternatively, you can add a handler to the Completed event, which will be raised when the method completes. However, this is slightly tricky to use, because it’s possible that the operation will already have run by the time you get around to adding the handler. It may be simpler just to write your code in a way that avoids using either of these. Remember that BeginInvoke calls the method you tell it to. If you need to do some work after the dispatcher has called your code, just add that to the method, as Example C-4 shows.

Example C-4. Avoiding Wait and Completed
MyDelegateType work = delegate {

    DoWorkOnUIThread(  );

    DoWhateverWeNeedToDoNowTheMainWorkHasBeenDone(  );
};
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, work);

Of course, both methods called in Example C-4 will run on the UI thread. If the second method is slow, just use a suitable multithreading or asynchronous invocation mechanism to move it back onto a worker thread.

When you call BeginInvoke, the dispatcher will run your method as soon as it is able to. If the UI thread is idle, this will happen immediately. This is not always desirable—it can be useful to be called back after a delay, which is what makes the dispatch timer useful.

DispatcherTimer

Applications often create timers in order to perform housekeeping tasks on a regular basis. You could use either of the Timer classes in the .NET class library, but both of these would notify you on a thread from the CLR thread pool, meaning you’d have to call Dispatcher.BeginInvoke to get back onto the right thread.

It is simpler to use the WPF-aware DispatcherTimer class. This raises timer notifications via the dispatcher, meaning your timer handler will always run on the correct thread automatically. This enables you to do things to the user interface directly from the handler, as Example C-5 shows.

Example C-5. Using a DispatcherTimer
partial class MyWindow : Window {

    DispatcherTimer dt;
    public MyWindow(  ) {
        dt = new DispatcherTimer(  );
        dt.Tick += dt_Tick;
        dt.Interval = TimeSpan.FromSeconds(2);
        dt.Start(  );
    }

    Random rnd = new Random(  );
    void dt_Tick(object sender, EventArgs e) {
        byte[] vals = new byte[3];
        rnd.NextBytes(vals);
        Color c = Color.FromRgb(vals[0], vals[1], vals[2]);

        // OK to touch UI elements, as the DispatcherTimer
        // calls us back on the UI thread
        this.Background = new SolidColorBrush(c);
    }
}

By default, the DispatcherTimer uses the Background priority level to deliver notifications. If necessary, you can change this by passing in a value from the DispatcherPriority enumeration when you construct the timer. You can also pass in a Dispatcher, although by default it will use the Dispatcher.CurrentDispatcher property to retrieve the dispatcher for the current thread. However, you will need to pass the dispatcher explicitly if you are creating the timer from a different thread than the UI thread.

Multiple UI Threads and Dispatchers

It is not strictly necessary for there to be just one UI thread—it is possible for an application to create user interface objects on several threads. However, all of the elements in any given window must belong to the same thread. So, in practice, you can have at most one UI thread per top-level window.

Tip

It is fairly rare to use more than one UI thread—the user can interact with only one window at a time, so there is normally no need for concurrent dispatchers. However, if for some reason, you cannot avoid blocking the UI thread, it might be appropriate to use multiple UI threads in order to localize the blocking to a single window. For example, suppose you need to host an unreliable or slow third-party UI component. Using one thread per top-level window would mean that if the component should freeze, it would take out only one window rather than the whole application. Internet Explorer uses multiple UI threads for this very reason. (Of course, IE isn’t a WPF application, but the same principle applies to Win32 applications.)

Each thread that hosts UI objects needs a dispatcher in order for those UI objects to function. In a single-threaded application, you don’t need to do anything special to create a dispatcher. The Application class creates one for you at startup, and shuts it down automatically on exit. However, if you create multiple user interface threads, you will need to start up and shut down the dispatcher for those manually. Example C-6 shows how to start a dispatcher.

Example C-6. Starting a dispatcher on a new UI thread
void StartDispatcher(  ) {
    Thread thread = new Thread(MyDispatcherThreadProc);
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start(  );
}

void MyDispatcherThreadProc(  ) {

    Window1 w = new Window1(  );
    w.Show(  );

    // Won't return until dispatcher shuts down
    System.Windows.Threading.Dispatcher.Run(  );
}

The Dispatcher for a thread is created automatically the first time an object derived from the DispatcherObject base class is created. All WPF classes derive from this base class. So, the Dispatcher for the new thread will come into existence when the Window is created. All we have to do is call the static Dispatcher.Run method to ensure that messages are delivered to any UI objects created on the thread. This method will not return until you call InvokeShutdown or BeginInvokeShutdown on the dispatcher.

Note

WPF will call InvokeShutdown for you on the dispatcher it creates for the application’s main thread. However, it is your responsibility to call this method for any other thread on which you call Dispatcher.Run. If you fail to do this, your application will continue to run even after the Application object shuts down the main thread.

The Dispatcher requires that you set the COM threading model to STA. Although a thread’s COM threading model is used only in COM interop scenarios, many system features rely on COM interop under the covers. The Dispatcher therefore requires the model to be set even if your application does not use any COM components directly. The call to SetApartmentState in Example C-6 ensures that the correct model is used.

Note

Although WPF does support the use of multiple UI threads, it does not support UI in multiple AppDomains. All the UI threads in a given process must be in the same AppDomain.

The Event-Based Asynchronous Pattern

Some components allow you to perform asynchronous work without having to worry about the details of the dispatcher. This is possible thanks to the event-based asynchronous pattern, which was introduced in NET 2.0. Components that implement this pattern manage the necessary thread switching for you. They do so using the AsyncOperationManager family of classes,[129] which abstract away the details of UI threading requirements, supporting both Windows Forms and WPF through a common API. This means that classes designed for use in Windows Forms applications will also work correctly in WPF applications.

The event-based asynchronous pattern is fairly simple. A class will provide one or more methods whose names end in Async. For example, the WebClient class in the System.Net namespace offers an UploadFileAsync method. Each asynchronous method has a corresponding event to signal completion—UploadFileCompleted, in this case. There may optionally be other events to indicate partial progress, such as the UploadProgressChanged event offered by WebClient. The crucial feature of the event-based asynchronous pattern is that the events are raised on the UI thread. For example, if you call UploadFileAsync from the UI thread of a WPF application, the object will raise the UploadFileCompleted event on the same thread.

Not all components offer this pattern. Fortunately, .NET provides an implementation of the pattern that you can use to wrap slow, synchronous code: the BackgroundWorker class.

BackgroundWorker

The BackgroundWorker class is defined in the System.ComponentModel namespace. It makes it easy to move slow work onto a worker thread in order to avoid making the UI unresponsive. It also provides a very simple way of sending progress and completion notifications back to the UI thread. It uses the AsyncOperationManager internally to implement the event-based asynchronous pattern, so in a WPF application it will use the Dispatcher under the covers when raising events.

Example C-7 shows the BackgroundWorker class in use. We start by attaching a handler to the DoWork event. This event will be raised on a worker thread, so we can do slow work in this event handler without causing the UI to become unresponsive. This example also handles the ProgressChanged and RunWorkerCompleted events. Your code can cause these to be raised to indicate that the work is progressing or has completed. Note that you will not get ProgressChanged events automatically. First, you must enable them by setting the WorkerReportsProgress property to true. Having enabled them, they will be raised only if the DoWork handler calls the ReportProgress method from time to time.

Example C-7. Using a BackgroundWorker
partial class MyWindow : Window {

    BackgroundWorker bw;

    public MyWindow(  ) {
        bw = new BackgroundWorker(  );
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += bw_ProgressChanged;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.WorkerReportsProgress = true;
        bw.RunWorkerAsync(  );
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {

        // Running on a worker thread
        for (int i = 0; i < 10; ++i) {
            int percent = i * 10;
            bw.ReportProgress(percent);
            Thread.Sleep(1000);
        }

        // The BackgroundWorker will raise the RunWorkerCompleted
        // event when this method returns.
    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) {
        // Running on a UI thread
        this.Title = "Working: " + e.ProgressPercentage + "%";
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        // Running on a UI thread
        this.Title = "Finished";
    }

When we call the RunWorkerAsync method, the BackgroundWorker raises the DoWork event on a worker thread. This means the DoWork handler can take as long as it likes, and will not cause the UI to freeze. Of course, it must not do anything to the user interface because it is not on the right thread. However, the ProgressChanged and RunWorkerCompleted events will always be raised on the UI thread, so it is always safe to use UI objects from these.

The RunWorkerCompleted handler is passed a RunWorkerCompletedEventArgs object. If there is a possibility that your DoWork method might throw an exception, you should check the Error property of this object. It will be null if the work completed successfully, and it will contain the exception otherwise.



[128] * It’s slightly more complex than that for these particular types. Brush and Geometry derive from DispatcherObject indirectly via the Freezable base class. This means they can be frozen, which has the effect of detaching them from their dispatcher and removing the thread affinity requirement. We discuss freezing in more detail in Appendix D.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset