Introduction
In the previous article “Learn more about how WPF Dispatcher works. (Invoke and InvokeAsync)”, we found that Dispatcher.Invoke
depends on Dispatcher.PushFrame
method to wait without blocking. But how does Dispatcher.PushFrame
work?
In this blog, we will introduce more details about Dispatcher
.
Details
The original link is 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分). But this blog was written in Chinese, so I translate its content to English. Waterlv is an MVP(Microsoft Most Valuable Professional), and he is good at .NET Core\WPF\.NET. Here is his Blog.
Dispatcher.PushFrame
If you are a WPF developer, you must have known the ShowDialog
method of the Window
class. But do you know how does ShowDialog
method work? Why does the method which calls ShowDialog
continue to execute after the window returns?
1 | var w = new FooWindow(); |
To answer these questions mentioned above, we have to read the source code of Dispatcher.PushFrame
method. But before reading, we should learn some knowledge about the DoEvents
method in Windows Forms
.
DoEvents
The DoEvents
method in the Windows Forms
allows you to insert a UI rendering operation during executing a time-consuming operation, and it makes your application look like it doesn’t stop responding.
Here are the codes of DoEvents
.
1 | [ ] |
Firstly, we should know the conclusion mentioned above that Dispacther.PushFrame
method can wait without blocking UI thread. ( The theory will be posted below, but now just remember this conclusion.)
And base on the conclusion, we now analyze the theory of DoEvents
which is posted above. And its steps are as followed.
Add an instance of
DispatcherOperation
withBackground
priority (4) to execute theExitFrame
method.Call the
Dispatcher.PushFrame
method to wait without blocking UI thread.Due to the priority of user’s input is
Input
(5) and the priority of the UI response isLoaded
(6) and the priority of rendering isRender
(7), they all are higher thanBackground
(4), so theExitFrame
method can only be executed after all UI task have been executed.The value of
Dispatcher.Continue
will be set tofalse
when executing theExitFrame
method.
Base on the function of DoEvents
, we can guess that the goal of setting the value of DispatcherFrame.Continue
to false
is to end the wait of Dispatcher.PushFrame(frame)
, so that the subsequent codes can be executed.
So according to the guess, we may know the steps of waiting without blocking UI thread.
Call
Dispatcher.PushFrame(frame)
to wait without blocking.Set
frame.Continue = false
to end the wait and execute the subsequent codes.
It is easier to read the source codes of the Dispatcher.PushFrame
with the guess and information above.
Source codes of PushFrame
Here are the codes.
1 | [ ] |
There are two points which need to pay attention to :
_frameDepth
field.- The codes which are surrounded by
while
.
Let’s begin with the _frameDepth
field. Every time you call the PushFrame
method, you should pass an instance of DispatcherFrame
, and the _frameDepth
field will add 1 when another PushFrame
is called during the period of PushFrame
. So, one by one, the DispatcherFrame
is nested in layers.
Then, we read the codes surrounded by while
.
1 | while(frame.Continue) |
Do you remember the guess mentioned above? After reading these codes we noticed that the condition of the while
is frame.Continue
, and if its value is false
the loop will be exited, and the PushFrame
method will be returned, then the _frameDepth
field will subtract 1. If all the frame.Continue
are set to false
, the Main
method will be exited.
And, what will happen if the value of frame.Continue
is always true? Obviously, it will enter a dead loop
. But you can’t insert any UI operations to a dead loop
, so how does it execute the UI operations? In the codes of this method, there are two possibilities, one is that GetMessage
method allows the application to continue processing window messages, the other is that TranslateAndDispatchMessage
method allows us to continue processing window messages. (The task queue of Dispatcher
depends on the message mechanism of windows which is mentioned in the previous article).
Unfortunately, both methods call the unmanaged code, it is hard to know their theories by reading the source codes. But, we can debug the .NET Framework
source code by source code debugging technology. And we found that GetMessage
method is running all the time, and the TranslateAndDispatchMessage
does not seem to be called. So we believe that the key to waiting without blocking is in the GetMessage
method. For more information about .NET Framework
source code debugging technology, visit 调试 ms 源代码 - 林德熙.
After reading the codes of GetMessage
, we found messagePump
which is an instance of UnsafeNagtiveMethods.ITfMessagePump
. And we can know how does message loop work by using the source code debugging technology.
Debugging the source code to get how does PushFrame
work
We add OnStylusDown
method for MainWindow
‘s StylusDown
event.
1 | private void OnStylusDown(object sender, StylusDownEventArgs e) |
In these codes, both Dispatcher.Invoke
and ShowDialog
methods will call PushFrame
. After running this application, every time we touch the MainWindow
, we can found that there will add two PushFrame
in the call-stack subwindow in VS. One is called by the Invoke
method and the other is called by ShowDialog
.
After each PushFrame
executes, there is a transition between host and manage. Then message processing is followed, and the touching message is called from the message processing.
So, it’s sure that every time we execute PushFrame
the unmanaged code will open a new message loop. When the window showed by ShowDialog
closed or the Invoke
has finished the message loop which is created by PushFrame
will be exited. Therefore, the subsequent codes which are blocked by while
can be executed. After the PushFrame
of the Main method is exited, this application will close.
Conclusions
- Every time the
PushFrame
executed, a new message loop will be created, and the_frameDepth
will add 1. - In the new message loop, it can handle all kinds of Window’s message, some of them are transmitted in the form of events, and some are tasks that are added to the
PriorityQueue<DispatcherOperation>
. - After exiting the
PushFrame
method, the message loop which is created by this code will be exited too. And the subsequent code of previousPushFrame
will be executed. - If all the
PushFrame
exited, the application will be closed. - The
While
loop inPushFrame
will block the main thread, but it can handle the messages in theloop
, so it looks like the main thread is not blocked.
The defects of PushFrame
PushFrame
depends on the Windows’ message loop, and there are some bugs about multiple message loops. Like,Base on
PushFrame
‘s block mechanism, the unexpected reentrancy problem may happen is a single thread application. visit 异步任务中的重新进入(Reentrancy) for more information about theReentrancy
.
References
PushFrame/DispatcherFrame
Message loop of Windows
- c# - PushFrame locks up WPF window when user is moving window - Stack Overflow