从库中捕获主线程SynchronizationContext或Dispatcher

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

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

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

我尝试过的事情:

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

当然,我可以让我的库的用户在调用我的Initialize方法时指定它是否是一个UI应用程序,但我希望尽可能避免该库的用户的复杂性。

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

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

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

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

将库初始化更改为具有SyncronizationContext参数。 如果参数为null,则库不需要执行任何特殊操作,如果不是null,则发布/发送GUI更新。

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

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

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

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

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