本文介绍了为什么一个独特的同步上下文对每个Dispatcher.BeginInvoke回调?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚注意到,与.NET 4.5每个 Dispatcher.BeginInvoke / InvokeAsync 回调在执行其自己非常独特的同步上下文( DispatcherSynchronizationContext 的实例)。 什么是这种变化背后的原因是什么?

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?

下面的琐碎WPF应用程序说明了这一点:

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();
        }
    }
}

输出:


same context #0: False
same context #1: False
same context #2: False
...

BaseCompatibility preferences.ReuseDispatcherSynchronizationContextInstance 设置为恢复.NET 4.0的行为:

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

留学的.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 的一个实例安装了正确的线程中执行。

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.

这种变化的一个有趣的副作用是,等待TaskCompletionSource.Task 继续(按 TaskCompletionSource.SetResult 触发)几乎总是异步的.NET 4.5的WPF,用不同的WinForms或WPF V4.0(更多详细信息)。

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

推荐答案

据与源$ C ​​$ C很长的注释说明。从4.5.1参考源的引用WPF的\ src \基地\ SYSTEM \的Windows \ BaseCompatibility preferences.cs:

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.

或者换一种说法,他们修复了这一错误在code:)

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

这篇关于为什么一个独特的同步上下文对每个Dispatcher.BeginInvoke回调?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-15 08:46