nos*_*tio 11 .net c# wpf multithreading synchronizationcontext
我刚刚注意到,使用.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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我不明白为什么可能需要这个,用Dispatcher.BeginInvoke
/ 排队的回调InvokeAsync
无论如何都要在已经DispatcherSynchronizationContext
安装了实例的正确线程上执行.
这种变化的一个有趣的副作用是,在.NET 4.5 WPF中,await TaskCompletionSource.Task
continuation(触发TaskCompletionSource.SetResult
)几乎总是异步的,与WinForms或v4.0 WPF(更多细节)不同.
它在源代码中用很长的注释来解释.引用wpf\src\Base\System\Windows\BaseCompatibilityPreferences.cs中的4.5.1引用源:
/// WPF 4.0 had a performance optimization where it would
/// frequently reuse the same instance of the
/// DispatcherSynchronizationContext when preparing the
/// ExecutionContext for invoking a DispatcherOperation. This
/// had observable impacts on behavior.
///
/// 1) Some task-parallel implementations check the reference
/// equality of the SynchronizationContext to determine if the
/// completion can be inlined - a significant performance win.
///
/// 2) But, the ExecutionContext would flow the
/// SynchronizationContext which could result in the same
/// instance of the DispatcherSynchronizationContext being the
/// current SynchronizationContext on two different threads.
/// The continuations would then be inlined, resulting in code
/// running on the wrong thread.
///
/// In 4.5 we changed this behavior to use a new instance of the
/// DispatcherSynchronizationContext for every operation, and
/// whenever SynchronizationContext.CreateCopy is called - such
/// as when the ExecutionContext is being flowed to another thread.
/// This has its own observable impacts:
///
/// 1) Some task-parallel implementations check the reference
/// equality of the SynchronizationContext to determine if the
/// completion can be inlined - since the instances are
/// different, this causes them to resort to the slower
/// path for potentially cross-thread completions.
///
/// 2) Some task-parallel implementations implement potentially
/// cross-thread completions by callling
/// SynchronizationContext.Post and Wait() and an event to be
/// signaled. If this was not a true cross-thread completion,
/// but rather just two seperate instances of
/// DispatcherSynchronizationContext for the same thread, this
/// would result in a deadlock.
Run Code Online (Sandbox Code Playgroud)
或者换句话说,他们修复了代码中的错误:)
我认为主要原因是4.5 DispatcherSynchronizationContext
还捕获了操作DispatcherPriority
,因此无法重用(此行为也可以通过配置BaseCompatibilityPreferences.FlowDispatcherSynchronizationContextPriority
).
关于await
- 由异步方法捕获的同步上下文与当前的同步上下文(由返回方式)SynchronizationContextAwaitTaskContinuation
存在参考相等性,如果不重用上下文,这当然会失败.因此,在调度程序上排队而不是内联执行的操作.SynchronizationContext.CurrentNoFlow
这也会影响SynchronizationContextTaskScheduler
,也会进行参考平等检查.
由于WPF和TPL是由不同的团队开发的,因此这两者都可能是一种疏忽.好像它是故意的.不过,在某些情况下,他们主动选择让异步延续更慢,这有点令人费解.他们不能改变行为以允许比较同步上下文的相等性(例如,通过重写Equals
和检查它属于同一个Dispatcher)?也许值得打开Connect问题.
归档时间: |
|
查看次数: |
1538 次 |
最近记录: |