【问题标题】:SynchronizationContext.Post(...) in transport event handler传输事件处理程序中的 SynchronizationContext.Post(...)
【发布时间】:2009-07-09 14:04:55
【问题描述】:

我们有一个方法,由于客户端应用程序中的线程需要使用 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 的请求的服务中。

这似乎是一种可以接受的确保我们不会出现跨线程错误的方法吗?如果没有,有没有更好的解决方案?

谢谢

【问题讨论】:

    标签: c# multithreading synchronization ynchronizationontext


    【解决方案1】:

    让我们追踪这个方法内部发生了什么,看看它告诉我们什么。

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

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

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

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

    5. 由于 Post() 调用,我们的事件处理程序方法现在再次被调用,使用完全相同的参数。然而,这一次,线程的 SynchronizationContext 和保存的上下文是一回事。跳过 if 子句的内容,执行 [code removed] 部分。

    这是一个好的设计吗?不知道 [code removed] 部分的内容很难说。以下是一些想法:

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

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

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

    4. 应该查看方法的 [code removed] 部分内部发生的情况。接触 UI 控件的代码完全属于那里——它必须在 UI 线程内执行。但是,如果那里有不处理 UI 的代码,最好让它在接收线程中执行。例如,任何 CPU 密集型解析都最好托管在接收线程中,它不会影响 UI 响应能力。您可以将这部分代码移到 if 子句上方,然后将其余代码移到单独的方法中 - 以确保两个部分都不会执行两次。

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

    【讨论】:

    • 谢谢,非常感谢。最后,我将此处理转移到 Stack 的子类中,该子类以指定的时间间隔进行监控。 [Code Remeoved] 区域内的代码以指定的时间间隔为堆栈上的每个成员执行。我认为性能下降的原因是,在 [Code Removed] 部分中,有一些调用仍未在 UI 线程上,导致此调用在能够在 UI 上执行之前多次递归线。将这些调用移至 MonitoredStack 解决了这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    相关资源
    最近更新 更多