Introduction
The WPF
developers cannot avoid using the Dispatcher
class. When we have to access controls or other UI parts on other threads (not UI
thread), we should use Dispatcher.InvokeAsync
or Dispatcher.Invoke
method. Also, Dispatcher
can be used to delay a task until the current task is executed.
Developers often used Dispatcher.BeginInvoke
in the past. And Microsoft adds a new method which’s name is Dispatcher.InvokeAsync
in .NET Framework 4.5
.
Are there any differences between these methods? And how does Dispatcher
work?
Details
The original link is 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分). 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.
BeginInvoke
and InvokeAsync
BeginInvoke
method has existed when the Dispatcher
class is added in the .NET framework 3.0
. Are you familiar with Begin
prefix? Yes, it is the Begin\End
asynchronous programming model(APM) which is introduced in .NET Framework 1.1
. Although BeginInvoke
is not completely implemented according to the APM model(there is no End and no IAsyncResult
result), but this method is also thread-related. Not only does the name carry Begin
for the means of asynchronous execution, but there are still ancient types like Delegate
in the parameter list. All the details above show that the BeginInvoke
method is an old API.
Every developer is still feeling excited about the async/await
asynchronous model which was introduced in .NET Framework 4.5
‘s updates. This model allows developers to write asynchronous codes as simple as synchronous codes. This is a new asynchronous programming model which is recommended by Microsoft, and its name is TAP
(Task-based Asynchronous pattern). And Dispatcher.InvokeAsync
was introduced in this version. So, what are the differences between BeginInvoke
and InvokeAsync
?
Differences
Firstly, we should read the source codes of these methods.
1 | [ ] |
There are five overload methods, and it is easy to find that three of them are marked by special attribute to make them invisible in IntelliSense list (but you can find them when you use ReSharper). Are there any differences among these methods? The answer is NO. After reading these methods’ code, we can find that they all call the same method named LegacyBeginInvokeImpl
. Here is the code of the method.
1 | [ ] |
And its name’s prefix Legacy
gives us some information about Microsoft’s altitude this method. It probably means Microsoft does not want to maintain this code anymore. Maybe, a few years later it will be marked as an Obsolete
method.
Here is the code of InvokeAsync
method.
1 | public DispatcherOperation InvokeAsync(Action callback); |
This is what the TAP code looks like. After reading these method’s code we can find that the implementation of these methods is almost the same. Here, I post the third method’s source code.
1 | [ ] |
What is the feeling when you read these codes? Yeah, there must be a voice in your mind and it says that ‘I read them before’. They are almost the same as LegacyBeginInvokeImpl
.
Finally, we knew the answer to why Microsoft recommended InvokeAsync
instead of BeginInvoke
. Microsoft had changed BeginInvoke
‘s implementation to TAP model but didn’t change its name and the parameters list in NET Framework 4.5
. So, they added another six methods(InvokeAsync
), and these all methods above do the same thing.
Due to InvokeAsync
and BeginInvoke
has the same codes, we don’t have to introduce both methods. So, in the following paragraphs, we are going to introduce more details about InvokeAsync
.
Thoery of InvokeAsync
All information above told us that the core of the InvokeAsync
method is InvokeAsyncImpl
. Here are the steps of the InvokeAsync
method.
Wrap the
Action/Func
withDispatcherOperation
.Therefore, the task and priority we passed will be processed together;Add
DispatcherOperation
to a queue ofPriorityQueue<DispatcherOperation>
. This queue implements aSortedList
class, every time it pops a task is depending on its priority.Call
RequestProcessing
, send a message to a hidden window at last.When the hidden window received that message, it will take a task from the
PriorityQueue
and execute it. (The real situation is more complicated, all details will be shown below).
In this progress, it calls TryPostMessage
to send messages. Here is the method’s code.
1 | UnsafeNativeMethods.TryPostMessage(new HandleRef(this, _window.Value.Handle), _msgProcessQueue, IntPtr.Zero, IntPtr.Zero); |
It is important to notice that there is a _window
, but what does this _window
mean? After reading the constructor of the Dispatcher
class, we found this _window
. It is not the Window
class we usually know, it a hidden window of Win32
, and its usage is to send and receive the schedule message of the Dispatcher
class. For more details about this window, visit MessageOnlyHwndWrapper
‘s source code, and you will find it calls UnsafeNativeMethods.CreateWindowEx
method to create this window in its base class HwndWrapper
.
Since it can send a message to _window
, it is natural to Hook
its message handler, like this:
1 | // Create the message-only window we use to receive messages |
In this WndProcHook
method, it handles three types of messages.
- Close the hidden window (
_window
). - Handle the scheduled task of the
Dispatcher
class (this message is registered at the static constructor method ofDispatcher
). - Timer.
Why does it handle the message about the timer?
After researching, it is clear that Microsoft split all priorities into three types.
- Foreground priority (from
DispatcherPriority.Loaded
toDispatcherPriority.Send
, the number is 6 to 10). - Background priority (from
DispatcherPriority.Background
toDispatcherPriority.Send
, the number is 4 to 5). - Idle priority (from
DispatcherPriority.SystemIdle
toDispatcherPriority.ApplicationIdle
, the number is 1 to 3)
But, it needs to notice that the Background priority
and Idle priority
are treated as one type of priority in Microsoft’s code. So, in fact, there are just two types of priority, the Foreground
and Background
. And there is one point to distinguish them is user’s input.
If a user’s input occurs, a timer will be turned on, and during this time interval, all Background priority
tasks will not be executed. But Foreground priority
tasks will not be affected by the user’s inputs. Therefore, the user’s inputs will not be starved, and the WPF application will not be stuck from the input level.
It’s important to notice that InvokeAsync
is implemented according to the TAP
asynchronous model, and this method can be used with await
. How does it work?
After reading the source code of InvokeAsync
which is posted above, we noticed that the return value of the InvokeAsync
method is an instance of DispatcherOperation
. This instance is created at the beginning of the codes. DispatcherOperation operation = new DispatcherOperation(this, priority, callback);
There is an Invoke
method without a return value, but it will change the result of Task
. And the DispatcherOperation
class has implemented the GetAwaiter
method so we can use await
before this method.
For more information about await
, see How to write a custom awaiter – Lucian’s VBlog.
The theory of Invoke
If you think the theory of Invoke
is easier than InvokeAsync
, probably you may ignore a very import question - deadlock
. If the Invoke
implemented according to the Synchronous wait
method, the UI thread will be blocked when it calls the Invoke
method on the current thread. And the Action
passed by Invoke
method will be executed on UI thread, but this thread is blocked, so deadlock
happens.
To avoid deadlock
, Invoke
method must have other implementation, and there are two branches for this implementation developed by Microsoft.
If the task’s priority is the highest priority (the number is 10), it will call the
action
passed byInvoke
method.If the task’s priority is lower than 10, it will call
DispatcherOperation.Wait
method to wait.
Here is a part of the source code of DispatcherOperation.Wait
method.
1 | public DispatcherOperationStatus Wait(TimeSpan timeout) |
In this method above, it calls Dispatcher.PushFrame
, and this method can wait without blocking the thread. For more theories visit Learn more about how WPF Dispatcher works.(Part 2 - PushFrame).
Conclusion
- We recommended using
InvokeAsync
instead ofBeginInvoke
if your application’s framework version is higher than 4.5. Dispatcher
execute allInvoke
tasks by priority by creating a hidden message window.Invoke
method avoids blocking UI thread by thePushFrame
method.
References
- Asynchronous model
- InvokeAsync
- WPF message mechanism
- Awaiter