11

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

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

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

我尝试过的事情:

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

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

4

4 回答 4

7

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

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

于 2012-06-11T16:29:30.353 回答
1

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

要回答您的问题,您可以使用一些“技巧”来确定您是否在控制台应用程序中运行。这个SO问题有一些信息:C#/.NET:检测程序是作为服务运行还是作为控制台应用程序运行

于 2012-06-11T15:12:07.090 回答
-1

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

于 2015-07-08T21:39:12.290 回答
-2

我认为这正是AsyncOperationManager.CreateOperation()它的用途。“实现基于事件的异步模式”</a> 指出:

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

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

在库中,我想在初始化期间捕获一些东西(一个 SynchronizationContext、一个调度程序、一个任务调度程序或其他东西)

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

于 2016-11-10T17:59:05.130 回答