使用SynchronizationContext将事件发送回WinForms或WPF的UI

我正在使用SynchronizationContext将事件封送到我的DLL中的UI线程,这个线程完成了大量的multithreading后台任务。

我知道singleton模式不是最喜欢的,但是我现在使用它来存储foo的父对象时UI的SynchronizationContext的引用。

public class Foo { public event EventHandler FooDoDoneEvent; public void DoFoo() { //stuff OnFooDoDone(); } private void OnFooDoDone() { if (FooDoDoneEvent != null) { if (TheUISync.Instance.UISync != SynchronizationContext.Current) { TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(); }, null); } else { FooDoDoneEvent(this, new EventArgs()); } } } } 

这在WPF中完全不起作用,TheUISync实例UI同步(源自主窗口的Feed)永远不会匹配当前的SynchronizationContext.Current。 在窗体中,当我做同样的事情时,他们会在调用之后匹配,然后我们会返回正确的线程。

我讨厌的修复,看起来像

 public class Foo { public event EventHandler FooDoDoneEvent; public void DoFoo() { //stuff OnFooDoDone(false); } private void OnFooDoDone(bool invoked) { if (FooDoDoneEvent != null) { if ((TheUISync.Instance.UISync != SynchronizationContext.Current) && (!invoked)) { TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(true); }, null); } else { FooDoDoneEvent(this, new EventArgs()); } } } } 

所以我希望这个例子足够有道理。

眼前的问题

您的直接问题是SynchronizationContext.Current不会自动设置为WPF。 要设置它,你需要在你的TheUISync代码中运行WPF时做这样的事情:

 var context = new DispatcherSynchronizationContext( Application.Current.Dispatcher); SynchronizationContext.SetSynchronizationContext(context); UISync = context; 

更深层次的问题

SynchronizationContext与COM +支持绑定,并被devise为跨线程。 在WPF中,不能有跨多个线程的分派器,所以一个SynchronizationContext不能真正跨线程。 SynchronizationContext可以切换到一个新的线程,特别是任何调用ExecutionContext.Run()的场景。 因此,如果您使用SynchronizationContext向WinForms和WPF客户端提供事件,则需要注意某些scheme将会中断,例如,在同一个进程中托pipe的Web服务或网站的Web请求将成为问题。

如何避免需要SynchronizationContext

因此,我build议使用专门用于此目的的WPF的Dispatcher机制,即使使用WinForms代码。 你已经创build了一个存储同步的“TheUISync”单例类,显然你有一些方法可以挂钩到应用程序的顶层。 不过,您可以添加一些代码,这些代码会将一些WPF内容添加到您的WinForms应用程序中,以便Dispatcher能够正常工作,然后使用下面介绍的新Dispatcher机制。

使用分派器而不是SynchronizationContext

WPF的Dispatcher机制实际上消除了对单独的SynchronizationContext对象的需要。 除非有特定的互操作场景,例如与COM +对象或WinForms UI共享代码,否则最好的解决scheme是使用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和BeginInvoke (或原始代码中的Post )调用委托的时间之间,可以将FooDoneEvent设置为null。 该修复是委托内部的第二个testing:

  if(FooDoDoneEvent!=null) Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action(() => { if(FooDoDoneEvent!=null) FooDoDoneEvent(this, new EventArgs()); })); 

为什么不让它担心呢? 那么这只是处理“无关联”情况的一个例子:

 static void RaiseOnUIThread(EventHandler handler, object sender) { if (handler != null) { SynchronizationContext ctx = SynchronizationContext.Current; if (ctx == null) { handler(sender, EventArgs.Empty); } else { ctx.Post(delegate { handler(sender, EventArgs.Empty); }, null); } } }