从库中捕获主线程SynchronizationContext或Dispatcher

时间:2012-06-11 13:58:13

标签: c# winforms multithreading synchronization

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

  • winforms应用程序
  • 本机应用程序(使用UI)
  • 控制台应用程序(没有用户界面)

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

我尝试过的事情:

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

4 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:-1)

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

答案 3 :(得分:-2)

我认为这正是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上下文对象句柄,该句柄会向消费者发出信号,表明他们需要管理其生命周期。