SynchronizationContext.Current 在异步回调中

SynchronizationContext.Current in async callback

我正在使用 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操作是在回调的worker线程中执行的

在 WinForms 中使用完全相同的代码,效果与我预期的一样。

目前,作为一种解决方法,我改为捕获当前 ManagedThreadId 并在回调中进行比较。正确的处理方法是什么?

更新:

我应该补充一点,我正在修改一个非常古老的现有 class,它目前使用以下结构:

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

我正在尝试删除 WinForms 依赖项,但不会对此 class 的客户端造成太大影响。 SomeFunction() 正在引发事件,所以如果我只调用 uiContext.Send() 或 uiContext.Post() ,执行顺序会改变,因为 Post() 将始终对调用进行排队,并且 Send() 将始终阻塞。

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

这是针对 .NET 4.0

Because in my case, the call to MyUIOperation() function would need to be called immediately if the ConnectedCallback function is called from the main thread.

这意味着如果 ConnectedCallback 在 UI 线程中调用,则对 MyUIOperation() 的调用将是阻塞调用,而不是 non-blocking 如果它被调用来自另一个线程。这 non-determinism 可能会导致其他问题。

只需调用 Send instead. According to this article,如果已经在 UI 线程中,对 Send 的调用将直接调用委托。

另外,您也可以 Dispatcher.Invoke() 代替。

事实证明,在 .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.