异步回调中的SynchronizationContext.Current

时间:2015-12-28 15:40:07

标签: wpf winforms asynchronous synchronizationcontext

我使用SynchronizationContext作为同步到GUI线程的方法,用于WinForms和WPF。最近我遇到了旧式异步回调问题:

   private void Button_Click(object sender, RoutedEventArgs e)
    {
        uiContext = SynchronizationContext.Current;

        var cl = new TcpClient();
        cl.BeginConnect("127.0.0.1", 22222, ConnectedCallback, null);
    }
    public void ConnectedCallback(IAsyncResult result)
    {
        if (SynchronizationContext.Current != uiContext)
            uiContext.Post(x => MyUIOperation(), null);
        else
            MyUIOperation();
    }

    public void MyUIOperation()
    {
        Title = "Connected";
    }

    private SynchronizationContext uiContext;

这将引发异常,因为回调函数中的SynchronizationContext.Current等于捕获的函数,因此UI操作在回调的工作线程中执行。

在WinForms中使用这个完全相同的代码可以像我预期的那样工作。

现在作为一种解决方法,我正在捕获当前的ManagedThreadId,并在回调中对它进行比较。处理这个问题的正确方法是什么?

更新

我应该补充一点,我正在修改一个当前使用以下构造的非常旧的现有类:

if (control.InvokeRequired())
    control.BeginInvoke(SomeFunction);
else
    SomeFunction();

我试图删除WinForms依赖项,而不会对此类的客户端产生太大影响。 SomeFunction()正在引发事件,所以如果我只调用uiContext.Send()或uiContext.Post(),则执行顺序会发生变化,因为Post()将始终对调用进行排队,而Send()将始终阻止。

此外,这只是显示我的问题根源的一小段代码。实际上,可以从主线程调用执行Post()的函数。

这是针对.NET 4.0

2 个答案:

答案 0 :(得分:3)

事实证明,在.NET 4.5中,SynchronizationContext实际上在回调函数中是不同的,if语句将评估为true。正如所讨论的here

,这是一次刻意的改变
   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.

答案 1 :(得分:2)

  

因为在我的情况下,需要调用MyUIOperation()函数   如果调用ConnectedCallback函数,则立即调用   主线。

这意味着如果在UI线程中调用MyUIOperation(),则对ConnectedCallback的调用将是阻塞调用,而如果从另一个线程调用它则是非阻塞调用。这种非决定论可能导致其他问题。

只需拨打Send即可。根据{{​​3}},对Send的调用只会在UI线程中直接调用该委托。

此外,您可以改为this article