使用 SynchronizationContext 将事件发送回 WinForms 或 WPF 的 UI
我使用 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());
}
}
}
}
所以我希望这个示例有足够的意义来遵循。
I'm using a SynchronizationContext to marshal events back to the UI thread from my DLL that does a lot of multi-threaded background tasks.
I know the singleton pattern isn't a favorite, but I'm using it for now to store a reference of the UI's SynchronizationContext when you create foo's parent object.
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());
}
}
}
}
This didn't work at all in WPF, the TheUISync instances UI sync (which is feed from the main window) never matches the current SynchronizationContext.Current. In windows form when I do the same thing they will match after an invoke and we'll get back to the correct thread.
My fix, which i hate, looks like
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());
}
}
}
}
So I hope this sample makes enough sense to follow.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
眼前的问题
您眼前的问题是
SynchronizationContext.Current
不会自动为 WPF 设置。要设置它,在 WPF 下运行时,您需要在 TheUISync 代码中执行类似以下操作:更深层次的问题
SynchronizationContext
与 COM+ 支持相关联,旨在跨线程。在 WPF 中,您无法拥有跨多个线程的 Dispatcher,因此一个SynchronizationContext
无法真正跨线程。在许多情况下,SynchronizationContext
可以切换到新线程 - 特别是任何调用ExecutionContext.Run()
的线程。因此,如果您使用 SynchronizationContext 向 WinForms 和 WPF 客户端提供事件,则需要注意某些情况会中断,例如对同一进程中托管的 Web 服务或站点的 Web 请求会是一个问题。如何避免需要 SynchronizationContext
因此,我建议专门为此目的使用 WPF 的
Dispatcher
机制,即使使用 WinForms 代码也是如此。您已经创建了一个“TheUISync”单例类来存储同步,因此显然您有某种方法可以连接到应用程序的顶层。无论您这样做,您都可以添加代码,将一些 WPF 内容添加到您的 WinForms 应用程序中,以便 Dispatcher 能够工作,然后使用我在下面描述的新的 Dispatcher 机制。使用 Dispatcher 代替 SynchronizationContext
WPF 的
Dispatcher
机制实际上消除了对单独的SynchronizationContext
对象的需要。除非您有某些互操作场景,例如与 COM+ 对象或 WinForms UI 共享代码,否则最好的解决方案是使用Dispatcher
而不是SynchronizationContext
。这看起来像:
请注意,您不再需要 TheUISync 对象 - WPF 会为您处理该详细信息。
如果您更喜欢旧的
delegate
语法,您可以这样做:需要修复的不相关错误
另请注意,原始代码中存在一个错误,被复制到这里。问题在于,在调用 OnFooDoDone 和
BeginInvoke
(或原始代码中的Post
)调用委托之间,FooDoneEvent 可以设置为 null。修复方法是委托内部的第二个测试:The immediate problem
Your immediate problem is that
SynchronizationContext.Current
is not automatically set for WPF. To set it you will need to do something like this in your TheUISync code when running under WPF:A deeper problem
SynchronizationContext
is tied in with the COM+ support and is designed to cross threads. In WPF you cannot have a Dispatcher that spans multiple threads, so oneSynchronizationContext
cannot really cross threads. There are a number of scenarios in which aSynchronizationContext
can switch to a new thread - specifically anything which callsExecutionContext.Run()
. So if you are usingSynchronizationContext
to provide events to both WinForms and WPF clients, you need to be aware that some scenarios will break, for example a web request to a web service or site hosted in the same process would be a problem.How to get around needing SynchronizationContext
Because of this I suggest using WPF's
Dispatcher
mechanism exclusively for this purpose, even with WinForms code. You have created a "TheUISync" singleton class that stores the synchronization, so clearly you have some way to hook into the top level of the application. However you are doing so, you can add code which creates adds some WPF content to your WinForms application so thatDispatcher
will work, then use the newDispatcher
mechanism which I describe below.Using Dispatcher instead of SynchronizationContext
WPF's
Dispatcher
mechanism actually eliminates the need for a separateSynchronizationContext
object. Unless you have certain interop scenarios such sharing code with COM+ objects or WinForms UIs, your best solution is to useDispatcher
instead ofSynchronizationContext
.This looks like:
Note that you no longer need a TheUISync object - WPF handles that detail for you.
If you're more comfortable with the older
delegate
syntax you can do it that way instead:An unrelated bug to fix
Also note that there is a bug in your original code that is replicated here. The problem is that FooDoneEvent can be set to null between the time OnFooDoDone is called and the time the
BeginInvoke
(orPost
in the original code) calls the delegate. The fix is a second test inside the delegate:与其和现在的相比,为什么不让它去担心呢?那么这只是处理“无上下文”情况的情况:
Rather than compare to the current one, why not just let it worry about it; then it is simply a case of handling the "no context" case: