代码之家  ›  专栏  ›  技术社区  ›  miguel

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

  •  2
  • miguel  · 技术社区  · 16 年前

    我们有一个方法,由于客户端应用程序中的线程,它需要使用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 回复  |  直到 15 年前
        1
  •  6
  •   Oren Trutner    16 年前

    让我们跟踪这个方法中发生的事情,看看它告诉我们什么。

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

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

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

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

    5. 作为post()调用的结果,事件处理程序方法现在再次被调用,参数完全相同。然而,这一次线程的同步上下文和保存的上下文是相同的。将跳过if子句的内容,并执行[code removed]部分。

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

    1. 从表面上看,这似乎不是一个可怕的设计。消息在后台线程上接收,并传递给UI线程进行表示。调用者立即返回执行其他操作,而接收者继续执行任务。这与unix fork()模式有些相似。

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

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

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

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

    推荐文章