【问题标题】:Capturing the main thread SynchronizationContext or Dispatcher from a library从库中捕获主线程 SynchronizationContext 或 Dispatcher
【发布时间】:2012-06-14 10:40:35
【问题描述】:

我有一个 C# 库,它希望能够将工作发送/发布到“主”ui 线程(如果存在)。 该库可供以下人员使用:

  • winforms 应用程序
  • 本机应用程序(带 UI)
  • 控制台应用程序(没有 UI)

在库中,我想在初始化期间捕获一些东西(A SynchronizationContext、Dispatcher、Task Scheduler 或其他东西),这将允许我(稍后)将工作发送/发布到主线程(如果主线程有这种能力——即它有一个消息泵)。例如,当且仅当主应用程序有能力让我进入主线程时,库才会在主线程上放置一些 Winforms UI。

我尝试过的事情:

  1. SynchronizationContext: 捕获这对于 Winforms 应用程序很好(WindowsFormsSynchronizationContext 将作为 Current SynchronizationContext 安装。这也适用于控制台应用程序——因为我可以检测到 Current SynchronizationContext 为空(因此,知道我没有能力向主线程发送/发布工作)。这里的问题是本机 UI 应用程序:它有能力(即它有一个消息泵),但是当前同步上下文为空,因此我可以'不要将它与控制台应用案例区分开来。如果我能区分,那么我可以简单地在主线程上安装一个 WindowsFormsSynchronizationContext,我很高兴。
  2. A Dispatcher:使用Current 捕获它会创建一个新的 SynchronizationContext。因此,在所有情况下,我都会取回一个 Dispatcher。但是,对于控制台应用程序,从后台线程使用 Dispatcher.Invoke 将挂起(如预期的那样)。我可以使用Dispatcher.FromThread(如果线程不存在,它不会为线程创建调度程序)。但是本机 UI 应用程序将使用此方法返回 null Dispatcher,因此我再次陷入无法区分 UI 应用程序和控制台应用程序的问题。
  3. TaskScheduler:我可以使用FromCurrentSynchronizationContext。这与 SynchronizationContext 有相同的问题。 IE。在调用 FromCurrentSynchronizationContext 之前,我必须检查 Current SynchronizationContext 是否为空(控制台应用程序和本机 ui 应用程序就是这种情况)。所以,我再次无法区分原生 ui 应用程序和控制台应用程序。

当然,我可以让我的库的用户在调用我的Initialize 方法时指定它是否是 UI 应用程序,但我希望尽可能避免库用户出现这种复杂情况。

【问题讨论】:

  • 我在 MFC 中使用 C# 库。这个库有 TaskScheduler.FromCurrentSynchronizationContext() 调用。当我运行 MFC 应用程序时,它会抛出异常,说“当前 SynchronizationContext 可能不能用作 TaskScheduler”。知道如何处理它吗?我没有直接在 MFC 中使用 C# 库,但我在托管 C++ 中创建了一个包装器,并且我在 MFC 应用程序中使用了这个包装器。

标签: c# winforms multithreading synchronization


【解决方案1】:

这通常是不可能的,一个易于在线程中使用的库不能对哪个特定线程是 UI 线程做出任何假设。您可以捕获 Synchronization.Current 但只有在从 UI 线程调用您的初始化方法时才能正常工作。这不是很不寻常的工作,就像 TaskScheduler.FromCurrentSynchronizationContext() 往往会偶然工作,但不能保证。您可以添加一个检查,如果 Thread.CurrentThread.GetApartmentState() 没有返回 STA,那么您没有被 UI 线程调用的可能性非常高。在这种情况下,SynchronizationContext.Current 通常也会为 null,这是另一种检查方式。

(可以说)更好的方法是不用担心它并让客户端代码解决它,它在编组回调时不会有任何问题。或者公开一个 SynchronizationContext 类型的属性,以便客户端代码可以分配它。或者将其添加为构造函数参数。如果您准备好发布但发现它仍然为空,则抛出 InvalidOperationException,这是客户端程序员仅进行一次的疏忽。

【讨论】:

    【解决方案2】:

    我认为您应该将此作为Initialize 方法的选项(或以某种方式允许您的调用者请求 UI 交互),对我来说这更有意义。我不知道具体细节,但在我看来,这些似乎是一件“有礼貌”的事情,让您的来电者决定他们是否想要您或想要支持您的 UI。我会更进一步,甚至作为提供同步上下文的调用者。但这是我的看法。

    要回答您的问题,您可以使用一些“技巧”来确定您是否在控制台应用程序中运行。这个 SO question 有一些信息:C#/.NET: Detect whether program is being run as a service or a console application

    【讨论】:

    • 谢谢。对于本机 MFC 应用程序,它们应该提供什么 SynchronizationContext? WindowsFormsSynchronizationContext 可以正常工作,但提供它对他们来说似乎有些奇怪。
    • 我将不得不做一些研究或咨询更熟悉 MFC 的人,自从我使用 MFC 以来已经真的很长时间了,所以我'我什至不确定。我的意思是,我不喜欢“融入”这些选择,开放和灵活通常是更好的选择。
    【解决方案3】:

    将库初始化更改为具有 SyncronizationContext 参数。如果参数为 null,则库不需要做任何特殊的事情,如果不是 null,则在那里发布/发送 GUI 更新。

    【讨论】:

      【解决方案4】:

      我认为这正是AsyncOperationManager.CreateOperation() 的用途。“Implementing the Event-based Asynchronous Pattern” 声明:

      基于事件的异步模式提供了一种标准化的方法来打包具有异步特性的类。如果使用 AsyncOperationManager 等帮助类实现,您的类将在任何应用程序模型下正常工作,包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序。

      由调用者决定是否要在 UI 线程上调用您的 API。如果他们这样做,这将捕获上下文,并且事件将按顺序通过消息泵。在控制台应用程序中,如果您安装一个 SynchronizationContext,您可以获得相同的行为,这样您就可以通过使用 Nito.AsyncEx nuget 包中的 AsyncContext.Run() 免费获得。不需要额外的属性或必须自己编写条件代码。如果没有可用的序列化同步上下文,AsyncOperation.Post() 将使用控制台应用程序可用的假同步上下文,这些应用程序只是将事件排队到线程池中(这意味着帖子可能不会按顺序执行)。完成后记得致电AsyncOperation.OperationCompleted()AsyncOperation.PostOperationCompleted()

      我想在初始化期间在库中捕获一些东西(A SynchronizationContext、Dispatcher、Task Scheduler 或其他东西)

      这正是AsyncOperationManager.CreateOperation() 所做的,并且以与环境无关的方式。但是您应该尝试将此与对 OperationCompleted() 的调用配对,考虑到您要公开的 API,这可能会更加困难。使用AsyncOperation 的最简单方法是在库实际启动操作时而不是在初始化期间启动操作。或者通过让初始化例程返回一个 IDisposable 上下文对象句柄,这将向消费者发出信号,表明他们需要管理其生命周期。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-05
        • 2014-10-06
        • 2011-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多