9

如何比较 SynchronizationContext?似乎同一个 Dispatcher 在使用 BeginInvoke 时可以创建不同的 SynchronizationContext。当我深入研究两个(不相等的)上下文时,我看到调度程序线程 ID 相同,但它们彼此不相等。

public partial class MainWindow : Window
{
    private SynchronizationContext contexta;
    private SynchronizationContext contextb;
    private SynchronizationContext contextc;
    private SynchronizationContext contextd;

    public MainWindow()
    {
        InitializeComponent();

        contexta = SynchronizationContext.Current;

        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        contextb = SynchronizationContext.Current;

        Dispatcher.Invoke(() =>
            {
                contextc = SynchronizationContext.Current;
            });

        Dispatcher.BeginInvoke(new Action(() =>
            {
                contextd = SynchronizationContext.Current;
            }));

        Debug.Assert(contexta != contextb);
        Debug.Assert(contexta == contextc);         // fails... why?!?!?
        Debug.Assert(contexta == contextd);         // fails... why?!?!?
        Debug.Assert(contextc == contextd);         // fails... why?!?!?
    }        

也许他们两个不能一起使用。我注意到这实际上有效:

        contexta.Send(new SendOrPostCallback((s) =>
            {
                contexte = SynchronizationContext.Current;
            }), null);

更新但奇怪的是,它并不总是有效。

    public override void AddRange(IEnumerable<T> items)
    {
        if (SynchronizationContext.Current == _context)
        {
            base.AddRange(items);
        }
        else
        {
            _context.Send(new SendOrPostCallback((state) =>
                {
                    AddRange(state as IEnumerable<T>);
                }), items);
        }
    }

例如,永远不会得到匹配的 _context 并永远持续下去。即使它不应该。后一个示例线程实际上最终是相同的,并且有一个上下文,但它是不同的。

Update2好的,我让它工作了,但我真的对此感到不舒服。显然,当您发布或发送时,您的任务是从正确的线程运行的,但如果您不是来自 UI,似乎会生成一个新的 SynchronizationContext。

    public override void AddRange(IEnumerable<T> items)
    {
        if (SynchronizationContext.Current == _context)
        {                       
            base.AddRange(items);
        }
        else
        {
            _context.Post(new SendOrPostCallback((state) =>
                {
                    if (SynchronizationContext.Current != _context)
                        SynchronizationContext.SetSynchronizationContext(_context);     // called every time.. strange
                    AddRange(items);
                }), null);
        }
    }

看看这个:

在此处输入图像描述

“需要直接调用者完全信任。部分信任或透明代码不能使用此成员。” :(

4

1 回答 1

2

我认为您对 BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance感兴趣。

此设置指示单个 SynchronizationContext 实例是否用于给定的 Dispatcher 对象。在 .net 4 之前默认为 true,在 .net 4.5 中为 false(这是 LukeN 观察到的行为变化)。

现在,如果您的目标只是直接拨打电话而不是拨打 .Send() 我会说:

  1. 调用 .Send() 时,如果您在正确的线程上(不使用调度程序队列),DispatcherSynchronizationContext 实际上只是直接调用,所以无论如何您不会获得太多(来自额外层的一些检查和调用)间接)。

  2. 如果您只针对 WPF 编写代码,则可以使用 Dispatcher.CheckAccess() 和 Dispatcher.Invoke() 来做您想做的事情。

在一般情况下,没有办法“比较”两个 SynchronizationContext,因此您只需调用 .Send()。无论如何,这不太可能是性能问题,请记住,过早的优化是万恶之源-> 首先测量。

于 2013-05-29T23:51:01.813 回答