I'm starting this Deep Dive into WPF series. By "deep dive" I mean striving to explain each topic thoroughly. Of course perfection is out of reach—if anything is wrong, friends are welcome to point it out; I'll keep supplementing the series and try to do it justice.
Typically, a WPF application starts with two threads: one for rendering and one for managing the UI. The rendering thread effectively runs hidden in the background, while the UI thread receives input, handles events, draws the screen, and runs application code.
The UI thread queues work items on an object called a Dispatcher. The Dispatcher selects work items based on priority and runs each work item until it completes. Every UI thread must have at least one Dispatcher, and each Dispatcher can execute work items on only one thread.
Those two paragraphs are MSDN's description of the WPF threading model. They introduce two main concepts: first, WPF splits threading in two—one for rendering, one for managing the UI; second, on the UI thread a class called Dispatcher helps process tasks.
So what are this threading model and the Dispatcher really like, and what are their strengths and weaknesses? Before formally analyzing them, let me start from an entry point I hope everyone can follow.
As a presentation foundation, WPF's mission is to build graphical user interfaces. On Windows, a graphical UI is built on the message mechanism. So what steps does it take to create a window?
- Register a window class.
WNDCLASSEX wcex;
RegisterClassEx(&wcex);
- Create the window.
CreateWindow(…);
ShowWindow(…);
UpdateWindow(…);
- Establish a message pump.
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
As an analogy: suppose we produce equipment in an automation plant. Following good practice, we first define a template for the equipment—that is registering the window class, where "class" mostly means "category." Once the template is ready we can formally produce the equipment—that is creating the window. CreateWindow matches our template (window class) by string. After successful creation, to make the equipment run we need something like blood flowing through a body, carrying commands to every part—that is the message pump. Like the heart, this pump continuously pulls messages via GetMessage and dispatches them, circulating blood (messages). Since we command the equipment via messages, we need a message queue to store them. In Windows the thread is the basic unit of scheduling, and the message queue lives on the thread. The loop's GetMessage patiently pulls messages from the current thread's queue and dispatches them to the corresponding windows.
So how does this play out specifically in WPF? How does it stay compatible with the old technology, and what new breakthroughs does it bring?
WPF introduces the concept of Dispatcher. The Dispatcher's main role is similar to Win32's message queue. Inside its functions it still calls the traditional window-class registration, window creation, and message pump setup. The Dispatcher itself is a singleton: the constructor is private, and a static CurrentDispatcher method is exposed to obtain the current thread's Dispatcher. The thread itself knows nothing about the Dispatcher. The Dispatcher maintains a static List _dispatchers; whenever CurrentDispatcher is called it walks this list, and if it doesn't find one it creates a new Dispatcher object and adds it to _dispatchers. The Dispatcher also maintains a Thread property: when created, it assigns the current thread to that property, and subsequent lookups use this field to match whether a Dispatcher for the current thread already exists in _dispatchers.
So when are window creation and the message pump established? Inside the Dispatcher there is an HwndWrapper field, and the Dispatcher's constructor calls HwndWrapper's constructor—window-class registration and window creation happen inside that call. The actual class is MessageOnlyHwndWrapper. Message-only is a common Windows programming trick: create a hidden window solely for dispatching messages. And when is the message-reading loop established?
The Dispatcher exposes a static Run method—literally, it starts the Dispatcher. Internally it calls PushFrame, where the familiar GetMessage and TranslateAndDispatchMessage can be found. What is this PushFrame, and where does the Frame concept come from?
This is a new concept WPF introduces: the nested message pump—starting a while (GetMessage(...)) inside another while (GetMessage(...)). Each call to PushFrame starts a new nested message pump. Each GetMessage pulls one message from the thread's queue; only when WM_QUIT is pulled does GetMessage return false. Windows handles GetMessage specially: when the queue is empty it suspends the executing thread, avoiding a busy loop. We'll return to the pros and cons of nested message pumps later. First, let's see how the Dispatcher processes tasks.
Windows defines many messages, prefixed with WM_. When registering a window class you set the window procedure; messages obtained by GetMessage are dispatched to that window procedure. The whole process looks like this:
This diagram comes from Hou Jie's classic book Inside MFC (《深入浅出MFC》). 1. First create the window and specify its window procedure WndProc. 2. When the window is created, a WM_CREATE is placed on the message queue. 3. The message pump obtains it with GetMessage and dispatches it to the window; the window procedure handles the WM_CREATE message…
So what role does WPF's Dispatcher play in this process? Steps 1, 2, and 3 are still the same. When the window procedure receives a message, it must translate the Windows message, by type, into an internal RoutedEvent or call layout functions, and so on. As mentioned earlier, the Dispatcher's main function is like Win32's message queue. What that queue stores is DispatcherOperation. As the name suggests, this wraps each work item into an object, roughly:
The queue type is PriorityQueue—a queue that carries priorities. WPF defines this priority as DispatcherPriority:
Calling DeQueue on this PriorityQueue takes the highest-priority task. So when are tasks added to this queue, and when are they taken out and executed?
The Dispatcher exposes two methods, Invoke and BeginInvoke, each with several overloads taking different parameters. Invoke ultimately calls BeginInvoke. A typical BeginInvoke signature is:
public DispatcherOperation BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args);
Inside BeginInvoke, the method and args are wrapped into a DispatcherOperation and added to the PriorityQueue according to priority; the return value is that DispatcherOperation. In other words, every call to Invoke or BeginInvoke adds a task to the Dispatcher—so when is that task executed?
DispatcherPriority defines many priorities, which WPF mainly divides into two groups: foreground priorities and background priorities. Foreground includes Loaded through Send; background includes Background through Input. The remaining priorities—except Invalid and Inactive—are idle priorities, processed in the same order as the background ones. The dividing line between foreground and background is Input, meaning keyboard input, mouse movement, clicks, and so on. Sources feeding the PriorityQueue include:
Of course, messages hooked at the Hwnd level ultimately also enter the Dispatcher's queue by calling Dispatcher's Invoke/BeginInvoke. When processing the PriorityQueue, the system first takes the highest-priority item. If it is a foreground priority, it executes. If it is a background priority, it scans the thread's message queue for input messages such as WM_MOUSEMOVE. If none exist, it executes. If such messages exist, it abandons execution and starts a timer; when the timer fires, it re-checks whether execution can proceed.
When is the PriorityQueue processed? When you call BeginInvoke to enqueue a work item, the system also evaluates whether to process the queue. The logic is similar: if the queue's highest priority is a foreground priority, PostMessage is called on the hidden window—this is a custom message registered by the Dispatcher via RegisterWindowMessage. When GetMessage pulls that custom message, the PriorityQueue is processed. If the highest priority is a background priority, the system scans the thread's message queue for input messages and decides whether to start a timer or post the message.
As an example, when a background thread uses Invoke to send a request to the UI thread, the flow is:
Invoke is called and the supplied
DispatcherPriorityis examined. If it is Send—a special priority—the thread context is switched directly, the task runs, and control returns. For any other priority, BeginInvoke is called.Inside BeginInvoke, the supplied Delegate and arguments are wrapped into a
DispatcherOperationand added to thePriorityQueue.The queue-processing request function is called, hoping to process the
PriorityQueue.If the queue's highest priority is a foreground priority, PostMessage is used to send the custom message to the hidden window. The background path is omitted here.
GetMessage obtains the message and dispatches it to the hidden window. This uses the common SubWindow technique (see note 1); the message is routed via a Hook to the
Dispatcher's WndProcHook function for processing.In WndProcHook, if the received window message is the
Dispatcher's custom message, thePriorityQueueis actually processed.Processing the
PriorityQueuetakes one task, performs the foreground/background priority check, and decides whether to handle it now or start a timer for later.
Now let's return to the nested message loop—it starts with modal dialogs. A typical modal dialog scenario looks like this:
SomeCodeA();
bool? result = dlg.ShowDialog();
SomeCodeB();
The code runs on the UI thread. When execution reaches dlg.ShowDialog, the modal dialog starts and waits for the user to click Yes/No or close it. After the dialog closes, the program continues with SomeCodeB. So the program must wait at SomeCodeB until ShowDialog returns. You could synchronize with a WaitHandle, but that requires suspending the current (UI) thread; if the main window has animations or other UI activity, they would stop and become unresponsive. WPF uses PushFrame here: ShowDialog establishes another message pump inside—while (GetMessage(...)). On one hand, messages on the UI thread can still be processed; on the other, because it is a while loop that returns when the dialog closes, the execution order of SomeCodeB is guaranteed.
Is this nested message loop really so perfect? Of course not. Opening one door also opens another. Consider: when a WM_SIZE message arrives, the layout system starts processing. If during that processing PushFrame is started, the nested message pump continues to pull messages from the queue. If the next message is also WM_SIZE, it gets processed. Suppose the nested pump returns after that message is handled; then the first WM_SIZE resumes. That's wrong—the intended order 1→2 has become 1→2→1. This situation isn't limited to layout, so WPF added a DisableProcessing function on the Dispatcher. During critical phases such as layout, this function stops pumping messages and forbids PushFrame.
In WPF, every WPF object derives from DispatcherObject, which exposes a Dispatcher property to obtain the Dispatcher of the thread that created the object. Due to thread affinity, a DispatcherObject may only be accessed by the thread that created it; other threads must obtain the corresponding Dispatcher and call Invoke or BeginInvoke to queue work. A UI thread has at least one Dispatcher that establishes the message pump and processes tasks, and one Dispatcher corresponds to only one UI thread. So what about the UI thread and the render thread?
As mentioned at the start, WPF splits threads in two: a UI thread and a render thread. They are designed as separate relationships and communicate through Channels (events). The quantity relationship is: a WPF process can have only one render thread, but can have one or more UI threads. Usually there is one UI thread—that is, one Dispatcher. So when would you need more?
Most of the time you don't. In a few cases—MediaElement, or hosting other ActiveX controls—we want them created on another thread for performance. You can create a new thread, create the control on it, and call Dispatcher.Run to start its Dispatcher. The main window and the new control then live on different threads; communication between them can use VisualTarget to connect the visual trees, or D3DImage to copy the new control into the main window for display.
As they say, beginnings are auspicious. WPF has no brand-new technology, but it introduces many new concepts. Like a beauty in makeup and packaging: lovely from a distance, but when the wind blows and a hand reaches out—huh, so that's all it is. ^_^
Note 1: SubWindow—subclassing a child window. Normally all windows of the same class share one message handler WndProc; a child window can call SetWindowLong to replace WndProc with SubWndProc. This is usually called sub-classing the window.




Top comments (0)