使用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); } } }