I've just noticed that with .NET 4.5 each Dispatcher.BeginInvoke/InvokeAsync callback is executed on its own very unique Synchronization Context (an instance of DispatcherSynchronizationContext). What's the reason behind this change?
The following trivial WPF app illustrates this:
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Action test = null;
var i = 0;
test = () =>
{
var sc = SynchronizationContext.Current;
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
Debug.Print("same context #" + i + ": " +
(sc == SynchronizationContext.Current));
if ( i < 10 )
{
i++;
test();
}
});
};
this.Loaded += (s, e) => test();
}
}
}
Output:
same context #0: False same context #1: False same context #2: False ...
Setting BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance to true restores the .NET 4.0 behavior:
public partial class App : Application
{
static App()
{
BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance = true;
}
}
same context #0: True same context #1: True same context #2: True ...
Studying the .NET sources for DispatcherOperation shows this:
[SecurityCritical]
private void InvokeImpl()
{
SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current;
try
{
// We are executing under the "foreign" execution context, but the
// SynchronizationContext must be for the correct dispatcher.
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(_dispatcher));
// Invoke the delegate that does the work for this operation.
_result = _dispatcher.WrappedInvoke(_method, _args, _isSingleParameter);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext);
}
}
I don't understand why this might be needed, the callbacks queued with Dispatcher.BeginInvoke/InvokeAsync are anyway executed on the correct thread which already has an instance of DispatcherSynchronizationContext installed on it.
One interesting side effect of this change is that await TaskCompletionSource.Task continuation (triggered by TaskCompletionSource.SetResult) is almost always asynchronous in .NET 4.5 WPF, unlike with WinForms or v4.0 WPF (some more details).