No matter how well architected, no UI framework will ever be able to provide a single threaded solution for every sort of problem. WPF comes close, but there are still situations where multiple threads improve user interface (UI) responsiveness or application performance. After discussing some background material, this paper will explore some of these situations. We’ll finish with a discussion of some lower level details for the particularly curious.
Overview and the Disptacher
Typical, WPF applications start their lives with two threads: one for handling rendering and another for managing the UI. The rendering thread effectively runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code. Most applications use a single UI thread, although in some situations it is best to use several. We’ll discuss this with an example later in the paper.
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.
The trick to building responsive, friendly applications is to maximize the Dispatcher throughput by keeping the work items small. This way items never get stale sitting in the Dispatcher queue waiting for processing. Any perceivable delay between input and response can frustrate a user.
How then are WPF applications supposed to handle big operations? What if my code involves a large calculation or needs to query a database on some remote server? Usually, the answer is to handle the big 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.
Historically, 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. A listbox could turn out looking very strange if its contents were updated by a background thread during painting.
WPF has a built in mutual exclusion mechanism that enforces this. Just about every class in WPF descends from DisptcherObject. At construction, a DisptcherObject stores a reference to the Dispatcher linked to the currently running thread. In effect, the DisptcherObject associates with the thread that creates it. During program execution, a DisptcherObject can call its public VerifyAccess method. VerifyAccess 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 DisptcherObject.
If 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. Detailed information about DispatcherPriority levels can be found in the Windows SDK documentation.