Learn more about how WPF Dispatcher works.(Invoke and InvokeAsync)

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
2
3
4
5
6
7
8
9
10
11
12
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public DispatcherOperation BeginInvoke(DispatcherPriority priority, Delegate method);

[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public DispatcherOperation BeginInvoke(DispatcherPriority priority, Delegate method, object arg);

[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public DispatcherOperation BeginInvoke(DispatcherPriority priority, Delegate method, object arg, params object[] args);

public DispatcherOperation BeginInvoke(Delegate method, params object[] args);

public DispatcherOperation BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args);

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
2
3
4
5
6
7
8
9
10
11
12
[SecuritySafeCritical]
private DispatcherOperation LegacyBeginInvokeImpl(DispatcherPriority priority, Delegate method, object args, int numArgs)
{
ValidatePriority(priority, "priority");
if(method == null)
{
throw new ArgumentNullException("method");
}
DispatcherOperation operation = new DispatcherOperation(this, method, priority, args, numArgs);
InvokeAsyncImpl(operation, CancellationToken.None);
return operation;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
public DispatcherOperation InvokeAsync(Action callback);

public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority);

[SecuritySafeCritical]
public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken);

public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback);

public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback, DispatcherPriority priority);

[SecuritySafeCritical]
public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback, DispatcherPriority priority, CancellationToken cancellationToken);

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
2
3
4
5
6
7
8
9
10
11
12
[SecuritySafeCritical]
public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
{
if(callback == null)
{
throw new ArgumentNullException("callback");
}
ValidatePriority(priority, "priority");
DispatcherOperation operation = new DispatcherOperation(this, priority, callback);
InvokeAsyncImpl(operation, cancellationToken);
return operation;
}

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.

  1. Wrap the Action/Func with DispatcherOperation.Therefore, the task and priority we passed will be processed together;

  2. Add DispatcherOperation to a queue of PriorityQueue<DispatcherOperation>. This queue implements a SortedList class, every time it pops a task is depending on its priority.

  3. Call RequestProcessing, send a message to a hidden window at last.

  4. 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
2
3
4
5
6
7
// Create the message-only window we use to receive messages
// that tell us to process the queue.
MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();
_window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window );

_hook = new HwndWrapperHook(WndProcHook);
_window.Value.AddHook(_hook);

In this WndProcHook method, it handles three types of messages.

  1. Close the hidden window (_window).
  2. Handle the scheduled task of the Dispatcher class (this message is registered at the static constructor method of Dispatcher).
  3. Timer.

Why does it handle the message about the timer?

After researching, it is clear that Microsoft split all priorities into three types.

  1. Foreground priority (from DispatcherPriority.Loaded to DispatcherPriority.Send, the number is 6 to 10).
  2. Background priority (from DispatcherPriority.Background to DispatcherPriority.Send, the number is 4 to 5).
  3. Idle priority (from DispatcherPriority.SystemIdle to DispatcherPriority.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.

  1. If the task’s priority is the highest priority (the number is 10), it will call the action passed by Invoke method.

  2. 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
2
3
4
5
6
7
8
9
10
11
12
13
public DispatcherOperationStatus Wait(TimeSpan timeout)
{
// Previous codes are omitted

// We are the dispatching thread for this operation, so
// we can't block. We will push a frame instead.
DispatcherOperationFrame frame = new DispatcherOperationFrame(this, timeout);
Dispatcher.PushFrame(frame);

// Behind codes are omitted

return _status;
}

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

  1. We recommended using InvokeAsync instead of BeginInvoke if your application’s framework version is higher than 4.5.
  2. Dispatcher execute all Invoke tasks by priority by creating a hidden message window.
  3. Invoke method avoids blocking UI thread by the PushFrame method.

References