传输事件处理程序中的SynchronizationContext.Post(...)

时间:2009-07-09 14:04:55

标签: c# multithreading synchronization ynchronizationontext

我们有一个方法,由于客户端应用程序中的线程需要使用SynchronizationContext。

我的一位同事编写的代码中有一些代码对我来说没有“感觉”,并且性能分析器告诉我退出这段代码中正在使用大量处理。

void transportHelper_SubscriptionMessageReceived(object sender, SubscriptionMessageEventArgs e)
        {
            if (SynchronizationContext.Current != synchronizationContext)
            {
                synchronizationContext.Post(delegate
                     {
                         transportHelper_SubscriptionMessageReceived(sender, e);
                     }, null);

                return;
            }
  [code removed....]
}

这对我来说感觉不对,因为我们基本上将相同的请求发布到gui线程事件队列...但是,除了这个代码区域的性能之外,我看不出任何明显的问题。

此方法是附加到由我们的中间层消息传递层帮助程序(transportHelper)引发的事件的事件处理程序,它存在于处理来自GUI的请求的服务中。

这似乎是确保我们不会遇到跨线程错误的可接受方式吗?如果没有,是否有更好的解决方案?

由于

1 个答案:

答案 0 :(得分:6)

让我们跟踪这个方法内部的内容,看看它告诉我们什么。

  1. 方法签名遵循事件处理程序的签名,正如问题所示,我们可以期望它首先在某个不是UI线程的线程的上下文中调用。

  2. 该方法所做的第一件事是将其运行的线程的SynchronizationContext与保存在成员变量中的SynchronizationContext进行比较。我们假设保存的上下文是UI线程的上下文。 (Mike Peretz在CodeProject上的SynchronizationContext类中发布了一系列优秀的介绍性文章)

  3. 该方法将发现上下文不相等,因为它在与UI线程不同的线程中调用。调用线程的上下文可能为null,其中UI线程的上下文几乎被保证设置为WindowsFormsSynchronizationContext的实例。然后它将在UI上下文中发出一个Post(),将委托传递给它自己及其参数,并立即返回。这样就完成了后台线程的所有处理。

  4. Post()调用导致在UI线程上调用完全相同的方法。跟踪WindowsFormsSynchronizationContext.Post()的实现表明,这是通过在UI线程的消息队列中排队自定义Windows消息来实现的。参数被传递,但不会被“编组”,因为它们不会被复制或转换。

  5. 我们的事件处理程序方法现在再次被调用,作为Post()调用的结果,具有完全相同的参数。但是,这一次,线程的SynchronizationContext和保存的上下文是同一个。跳过if子句的内容,并执行[删除代码]部分。

  6. 这是一个好设计吗?如果不知道[删除代码]部分的内容,很难说。以下是一些想法:

    1. 从表面上看,这似乎并不是一个糟糕的设计。在后台线程上接收消息,并将其传递给UI线程以进行演示。调用者立即返回做其他事情,接收者继续执行任务。这有点类似于Unix fork()模式。

    2. 该方法以独特的方式递归。它不会在同一个线程上调用自己。相反,它会导致不同的线程调用它。与任何递归代码一样,我们会关注它的终止条件。通过阅读代码,假设在传递给UI线程时,它将始终以递归方式被调用一次,这似乎是相当安全的。但这是另一个需要注意的问题。另一种设计可能已经将一种不同的方法传递给Post(),也许是一种匿名方法,并完全避免了递归问题。

    3. 似乎没有明显的理由在if子句中进行大量处理。使用.NET reflector查看Post()的WindowsFormsSynchronizationContext实现会在其中显示一些中等长度的代码序列,但没有什么太花哨的;这一切都发生在RAM中,并且它不会复制大量数据。基本上它只是准备参数并在接收线程的消息队列中排队Windows消息。

    4. 应该审核方法中[代码删除]部分内的内容。触摸UI控件的代码完全属于那里 - 它必须在UI线程内执行。但是,如果那里的代码没有处理UI,那么让它在接收线程中执行可能是个更好的主意。例如,任何CPU密集型解析都将更好地托管在接收线程中,它不会影响UI响应。您可以将代码部分移到if子句上方,并将剩余代码移到单独的方法中 - 以确保两部分都不会被执行两次。

    5. 如果接收线程和UI线程都需要保持响应,例如为了进一步传入消息和用户输入,您可能需要引入第三个线程来处理消息,然后再将它们传递给UI线程。