从SynchonizationContext派生

简而言之,我已经实现了一个派生自SynchronizationContext的类,以便GUI应用程序可以轻松使用在GUI线程以外的线程上引发的事件。 我非常感谢对我的实施的评论。 具体来说,你有什么建议反对或可能导致我没有预料到的问题吗? 我最初的测试成功了。

长版本:我目前正在开发分布式系统(WCF)的业务层,它使用回调将事件从服务器传播到客户端。 我的设计目标之一是提供可绑定的业务对象(即INotifyPropertyChanged / IEditableObject等),以便在客户端轻松使用这些对象。 作为其中的一部分,我提供了回调接口的实现,该接口在事件进入时处理事件,更新业务对象,进而引发属性更改事件。 因此,我需要在GUI线程上引发这些事件(以避免跨线程操作exception)。 因此,我尝试提供自定义SynchronizationContext,实现回调接口的类使用它来将事件传播到GUI线程。 此外,我希望此实现独立于客户端环境 – 例如WinForms GUI应用程序或ConsoleApp或其他东西。 换句话说,我不想假设静态SynchronizationContext.Current可用。 因此我使用ExecutionContext作为后备策略。

public class ImplicitSynchronisationContext : SynchronizationContext 

{

 private readonly ExecutionContext m_ExecContext; private readonly SynchronizationContext m_SyncContext; public ImplicitSynchronisationContext() { // Default to the current sync context if available. if (SynchronizationContext.Current != null) { m_SyncContext = SynchronizationContext.Current; } else { m_ExecContext = ExecutionContext.Capture(); } } public override void Post(SendOrPostCallback d, object state) { if (m_SyncContext != null) { m_SyncContext.Post(d, state); } else { ExecutionContext.Run( m_ExecContext.CreateCopy(), (object args) => { ThreadPool.QueueUserWorkItem(new WaitCallback(this.Invoker), args); }, new object[] { d, state }); } } public override void Send(SendOrPostCallback d, object state) { if (m_SyncContext != null) { m_SyncContext.Send(d, state); } else { ExecutionContext.Run( m_ExecContext.CreateCopy(), new ContextCallback(this.Invoker), new object[] { d, state }); } } private void Invoker(object args) { Debug.Assert(args != null); Debug.Assert(args is object[]); object[] parts = (object[])args; Debug.Assert(parts.Length == 2); Debug.Assert(parts[0] is SendOrPostCallback); SendOrPostCallback d = (parts[0] as SendOrPostCallback); d(parts[1]); } 

}

不幸的是你写了一些已经存在的东西 SynchronizationContext类完全按照您的方式执行。 向主类添加属性,类似于:

  public static SynchronizationContext SynchronizationContext { get { if (SynchronizationContext.Current == null) { SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } return SynchronizationContext.Current; } } 

或者使用AsyncOperationManager.SynchronizationContext,它完全相同。 当然是优选的。

我认为上面的代码在技术上没有任何问题。

但是,它比实际需要更复杂。 没有真正的理由复制ExecutionContext并在其中运行操作。 这通过调用ThreadPool.QueueUserWorkItem自动发生。 有关详细信息,请参阅ExecutionContext的文档:

在应用程序域中,只要传输线程,就必须传输整个执行上下文。 在Thread.Start方法,大多数线程池操作和Windows窗体线程通过Windows消息泵进行传输期间发生这种情况。

就个人而言,我会放弃跟踪ExecutionContext,除非确实需要它,并将其简化为:

 public class ImplicitSynchronisationContext : SynchronizationContext { private readonly SynchronizationContext m_SyncContext; public ImplicitSynchronisationContext() { // Default to the current sync context if available. m_SyncContext = SynchronizationContext.Current; } public override void Post(SendOrPostCallback d, object state) { if (m_SyncContext != null) { m_SyncContext.Post(d, state); } else { ThreadPool.QueueUserWorkItem(_ => d(state)); } } public override void Send(SendOrPostCallback d, object state) { if (m_SyncContext != null) { m_SyncContext.Send(d, state); } else { d(state); } } } 

我对你写这门课的动机有点不确定。

如果您使用的是WinForms或WPF,它们提供了使用SynchronizationContext.Current可用的实现。

如果您在控制台应用程序中,则控制主线程。 你是如何与这个线程沟通的?

如果您正在运行Windows消息循环,可能是您正在使用WinForms或WPF。

如果您正在等待生产者/消费者队列,那么您的主线程将是消耗事件的线程,因此根据定义,您将位于主线程上。

缺口

非常感谢大家的反馈。

Hans Passant的回答促使我发展/改变我的解决方案。

回顾一下,我的问题本质上是如何从我的WCF服务获取异步回调以传播到客户端的UI线程(WinForms或WPF),而无需客户端开发人员的任何工作。

我已经放弃了上面提供的实现,因为它是多余的。 我的服务回调契约的实现现在只有一个重载的构造函数,它采用bool synchroniseCallbacks。 如果是,我存储对AsyncOperationManager.SynchronizationContext的引用。 当事件从我的服务进入时,我使用该同步上下文发布或发送它们。

正如汉斯指出的那样,使用AsyncOperationManager公开的同步上下文的好处是它永远不会为空,而且,在WinForms和WPF等GUI应用程序中,它将返回UI线程的同步上下文 – 问题解决了!

干杯!