Я только что заметил, что в .NET 4.5 каждый обратный вызов Dispatcher.BeginInvoke
/InvokeAsync
выполняется в своем собственном уникальном контексте синхронизации (экземпляр DispatcherSynchronizationContext
). В чем причина этого изменения?
Это иллюстрирует следующее тривиальное приложение WPF:
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();
}
}
}
Вывод:
same context #0: False same context #1: False same context #2: False ...
Установка BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance
на true
восстанавливает поведение .NET 4.0:
public partial class App : Application
{
static App()
{
BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance = true;
}
}
same context #0: True same context #1: True same context #2: True ...
Изучение исходников .NET для DispatcherOperation
показывает следующее:
[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);
}
}
Я не понимаю, зачем это может понадобиться, обратные вызовы, поставленные в очередь с Dispatcher.BeginInvoke
/InvokeAsync
, в любом случае выполняются в правильном потоке, на котором уже установлен экземпляр DispatcherSynchronizationContext
.
Одним интересным побочным эффектом этого изменения является то, что продолжение await TaskCompletionSource.Task
(запускаемое TaskCompletionSource.SetResult
) почти всегда асинхронно в .NET 4.5 WPF, в отличие от WinForms или v4.0 WPF (подробнее).