我正在使用SynchronizationContext将事件编组回我的DLL中的UI线程,该线程执行许多多线程后台任务。
我知道单例模式不是最喜欢的,但我现在用它来存储创建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同步(从主窗口馈送)永远不会与当前的SynchronizationContext.Current匹配。在Windows窗体中,当我执行相同的操作时,它们将在调用后匹配,然后我们将返回到正确的线程。
我讨厌的修复,看起来像
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());
}
}
}
}
所以我希望这个样本足以让人理解。
答案 0 :(得分:37)
即时问题
您当前的问题是,SynchronizationContext.Current
不会自动为WPF设置。要设置它,您需要在WPF下运行时在TheUISync代码中执行类似的操作:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
更深层次的问题
SynchronizationContext
与COM +支持捆绑在一起,旨在跨线程。在WPF中,您不能拥有跨多个线程的Dispatcher,因此一个SynchronizationContext
无法真正跨越线程。在许多情况下,SynchronizationContext
可以切换到新线程 - 特别是调用ExecutionContext.Run()
的任何内容。因此,如果您使用SynchronizationContext
向WinForms和WPF客户端提供事件,则需要注意某些方案会中断,例如,对同一进程中托管的Web服务或站点的Web请求将是问题
如何避开需要SynchronizationContext
因此我建议使用WPF的Dispatcher
机制专门用于此目的,即使使用WinForms代码也是如此。您已经创建了一个存储同步的“TheUISync”单例类,因此很明显您可以通过某种方式连接到应用程序的顶层。但是,您正在这样做,您可以添加创建的代码,为您的WinForms应用程序添加一些WPF内容,以便Dispatcher
可以工作,然后使用我在下面描述的新Dispatcher
机制。
使用Dispatcher而不是SynchronizationContext
WPF的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());
}));
要修复的无关错误
另请注意,您的原始代码中存在一个在此处复制的错误。问题是FooDoneEvent可以在调用OnFooDoDone的时间和BeginInvoke
(或原始代码中的Post
)调用委托的时间之间设置为null。该修复是委托内的第二个测试:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));
答案 1 :(得分:0)
为什么不让它担心呢?那么这只是一个处理“无上下文”案例的案例:
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);
}
}
}