Joe*_*tti 11 .net wpf multithreading synchronizationcontext
我正在使用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());
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这在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());
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以我希望这个样本足够有意义.
Ray*_*rns 38
眼前的问题
您的直接问题是SynchronizationContext.Current
不会自动为WPF设置.要设置它,您需要在WPF下运行时在TheUISync代码中执行类似的操作:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
Run Code Online (Sandbox Code Playgroud)
更深层次的问题
SynchronizationContext
与COM +支持捆绑在一起,旨在交叉线程.在WPF中,您不能拥有跨多个线程的Dispatcher,因此SynchronizationContext
无法真正跨线程.有许多场景SynchronizationContext
可以切换到新线程 - 特别是任何调用的线程ExecutionContext.Run()
.因此,如果您使用SynchronizationContext
向WinForms和WPF客户端提供事件,则需要注意某些方案会中断,例如,对Web服务或在同一进程中托管的站点的Web请求将是一个问题.
如何绕过需要SynchronizationContext
因此,我建议Dispatcher
专门为此目的使用WPF的机制,即使使用WinForms代码.您已经创建了一个存储同步的"TheUISync"单例类,因此很明显您可以通过某种方式连接到应用程序的顶层.但是,您正在这样做,您可以添加代码,创建将一些WPF内容添加到您的WinForms应用程序,以便它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());
}));
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您不再需要TheUISync对象 - WPF会为您处理该详细信息.
如果您对旧delegate
语法更熟悉,可以这样做:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(delegate
{
FooDoDoneEvent(this, new EventArgs());
}));
Run Code Online (Sandbox Code Playgroud)
一个无关的错误修复
另请注意,您的原始代码中存在一个在此处复制的错误.问题是FooDoneEvent可以在调用OnFooDoDone的时间和BeginInvoke
(或Post
在原始代码中)调用委托的时间之间设置为null .该修复是委托内的第二个测试:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));
Run Code Online (Sandbox Code Playgroud)