Why a unique synchronization context for each Dispatcher.BeginInvoke callback?

浪子不回头ぞ 提交于 2019-11-28 10:01:59

It is explained with a very long comment in the source code. Quoting from the 4.5.1 Reference Source in wpf\src\Base\System\Windows\BaseCompatibilityPreferences.cs:

    ///     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.

Or to put it another way, they fixed the bug in your code :)

I believe the main reason is that the 4.5 DispatcherSynchronizationContext also captures the operation's DispatcherPriority, so it cannot be reused (this behavior is also configurable via BaseCompatibilityPreferences.FlowDispatcherSynchronizationContextPriority).

Regarding await - in SynchronizationContextAwaitTaskContinuation there's a referencial equality for the synchronization context captured by the async method to the current one (returned by SynchronizationContext.CurrentNoFlow), which of course fails if the context isn't reused. So the operation to gets queued on the dispatcher instead of being executed inline.

This also affects SynchronizationContextTaskScheduler, which also performs a referencial equality check.

Both of these may have been an oversight due to the fact WPF and TPL are developed by different teams. Seems like it was done on purpose. Still, it's a bit puzzling they actively chose to make async continuations slower in some cases. Couldn't they change the behavior to allow comparing the sync context's for equality (for example, by overriding Equals and checking it belongs to the same Dispatcher)? Maybe it's worth opening a Connect issue.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!