眼前的问题
你眼前的问题是SynchronizationContext.Current
不会自动为 WPF 设置。要设置它,当在 WPF 下运行时,您需要在 TheUISync 代码中执行类似的操作:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
更深层次的问题
SynchronizationContext
与 COM+ 支持结合在一起,并且被设计为跨线程。在 WPF 中,您不能拥有跨多个线程的调度程序,因此SynchronizationContext
不能真正跨线程。在许多情况下,SynchronizationContext
可以切换到一个新线程 - 特别是任何调用ExecutionContext.Run()
。所以如果你正在使用SynchronizationContext
要向 WinForms 和 WPF 客户端提供事件,您需要注意某些情况会中断,例如对托管在同一进程中的 Web 服务或站点的 Web 请求将会出现问题。
如何避免需要 SynchronizationContext
因此我建议使用 WPFDispatcher
专门用于此目的的机制,即使使用 WinForms 代码也是如此。您已经创建了一个“TheUISync”单例类来存储同步,因此显然您有某种方法可以连接到应用程序的顶层。无论您这样做,您都可以添加代码,将一些 WPF 内容添加到您的 WinForms 应用程序中,以便Dispatcher
会起作用,然后使用新的Dispatcher
我在下面描述的机制。
使用 Dispatcher 代替 SynchronizationContext
WPF's Dispatcher
机制实际上消除了对单独的SynchronizationContext
目的。除非您有某些互操作场景,例如与 COM+ 对象或 WinForms UI 共享代码,否则最好的解决方案是使用Dispatcher
代替SynchronizationContext
.
这看起来像:
public class Foo
{
public event EventHandler FooDoDoneEvent;
public void DoFoo()
{
//stuff
OnFooDoDone();
}
private void OnFooDoDone()
{
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
FooDoDoneEvent(this, new EventArgs());
}));
}
}
请注意,您不再需要 TheUISync 对象 - WPF 会为您处理该详细信息。
如果你更喜欢与年长的人相处delegate
语法你可以这样做:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(delegate
{
FooDoDoneEvent(this, new EventArgs());
}));
需要修复的不相关错误
另请注意,您的原始代码中存在一个错误,该错误已在此处复制。问题在于,在调用 OnFooDoDone 的时间和调用 OnFooDoDone 的时间之间,FooDoneEvent 可以设置为 null。BeginInvoke
(or Post
在原始代码中)调用委托。修复方法是委托内部的第二个测试:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));