简而言之,我实现了一个派生自SynchronizationContext的类,使GUI应用程序可以轻松使用在GUI线程以外的线程上引发的事件。我非常感谢对我的实施的评论。具体来说,你有什么建议反对或可能导致我没有预料到的问题吗?我的初步测试已经成功。
长版: 我目前正在开发分布式系统(WCF)的业务层,它使用回调将事件从服务器传播到客户端。我的设计目标之一是提供可绑定的业务对象(即INotifyPropertyChanged / IEditableObject等),以便在客户端轻松使用这些对象。作为其中的一部分,我提供了一个回调接口的实现,它在事件进入时处理事件,更新业务对象,进而引发属性更改事件。因此,我需要在GUI线程上引发这些事件(以避免跨线程操作异常)。因此,我尝试提供自定义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]);
}
}
答案 0 :(得分:2)
不幸的是你写了一些已经存在的东西。 SynchronizationContext类完全按照您的方式执行。将属性添加到主类,类似于:
public static SynchronizationContext SynchronizationContext {
get {
if (SynchronizationContext.Current == null) {
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
return SynchronizationContext.Current;
}
}
或者使用AsyncOperationManager.SynchronizationContext,它完全相同。当然是优选的。
答案 1 :(得分:1)
我认为上面的代码在技术上没有任何问题..
然而,它比实际需要更复杂。没有真正的理由复制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);
}
}
}
答案 2 :(得分:0)
我对你写这门课的动机有点不确定。
如果您使用的是WinForms或WPF,它们会提供使用SynchronizationContext.Current提供的实现。
如果您在控制台应用程序中,则控制主线程。你是如何与这个线程沟通的?
如果您正在运行Windows消息循环,可能是您正在使用WinForms或WPF。
如果您正在等待生产者/消费者队列,您的主线程将是消费事件的线程,因此根据定义,您将在主线程上。
尼克
答案 3 :(得分:0)
非常感谢大家的反馈。
Hans Passant的回答促使我发展/改变我的解决方案。
回顾一下,我的问题主要是如何从我的WCF服务获取异步回调以传播到客户端的UI线程(WinForms或WPF),而不需要客户端开发人员的任何工作。
我放弃了上面提供的实施,因为它是多余的。我的服务回调契约的实现现在只有一个重载的构造函数,它接受bool synchroniseCallbacks。如果是,我存储对AsyncOperationManager.SynchronizationContext的引用。当事件从我的服务进入时,我使用该同步上下文发布或发送它们。
正如Hans指出的那样,使用AsyncOperationManager公开的同步上下文的好处是它永远不会为null,而且,在WinForms和WPF等GUI应用程序中,它将返回UI线程的同步上下文 - 问题解决了!干杯!