.net 使用 SynchronizationContext 将事件发送回 WinForms 或 WPF 的 UI
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1949789/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using SynchronizationContext for sending events back to the UI for WinForms or WPF
提问by Joel Barsotti
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.
我正在使用 SynchronizationContext 将事件从我的 DLL 编组回 UI 线程,该线程执行许多多线程后台任务。
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.
我知道单例模式不是我最喜欢的,但我现在使用它来在您创建 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());
}
}
}
}
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.
这在 WPF 中根本不起作用,TheUISync 实例 UI 同步(从主窗口提供)永远不会匹配当前的 SynchronizationContext.Current。在 Windows 窗体中,当我做同样的事情时,它们将在调用后匹配,我们将返回到正确的线程。
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.
所以我希望这个样本有足够的意义可以遵循。
回答by Ray Burns
The immediate problem
眼前的问题
Your immediate problem is that SynchronizationContext.Currentis not automatically set for WPF. To set it you will need to do something like this in your TheUISync code when running under WPF:
您的直接问题是SynchronizationContext.Current不会自动为 WPF 设置。要设置它,您需要在 WPF 下运行时在 TheUISync 代码中执行以下操作:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
A deeper problem
更深层次的问题
SynchronizationContextis tied in with the COM+ support and is designed to cross threads. In WPF you cannot have a Dispatcher that spans multiple threads, so one SynchronizationContextcannot really cross threads. There are a number of scenarios in which a SynchronizationContextcan switch to a new thread - specifically anything which calls ExecutionContext.Run(). So if you are using SynchronizationContextto 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.
SynchronizationContext与 COM+ 支持捆绑在一起,旨在跨线程。在 WPF 中,您不能拥有跨多个线程的 Dispatcher,因此SynchronizationContext不能真正跨线程。在许多情况下 aSynchronizationContext可以切换到新线程 - 特别是任何调用ExecutionContext.Run(). 因此,如果您使用SynchronizationContext向 WinForms 和 WPF 客户端提供事件,则需要注意某些场景会中断,例如,对同一进程中托管的 Web 服务或站点的 Web 请求将是一个问题。
How to get around needing SynchronizationContext
如何绕过需要 SynchronizationContext
Because of this I suggest using WPF's Dispatchermechanism 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 that Dispatcherwill work, then use the new Dispatchermechanism which I describe below.
因此,我建议Dispatcher专门为此目的使用 WPF 的机制,即使使用 WinForms 代码也是如此。您已经创建了一个用于存储同步的“TheUISync”单例类,因此很明显您可以通过某种方式连接到应用程序的顶层。无论您这样做,您都可以添加创建的代码,将一些 WPF 内容添加到您的 WinForms 应用程序中,以便Dispatcher工作,然后使用Dispatcher我在下面描述的新机制。
Using Dispatcher instead of SynchronizationContext
使用 Dispatcher 而不是 SynchronizationContext
WPF's Dispatchermechanism actually eliminates the need for a separate SynchronizationContextobject. Unless you have certain interop scenarios such sharing code with COM+ objects or WinForms UIs, your best solution is to use Dispatcherinstead of SynchronizationContext.
WPF 的Dispatcher机制实际上消除了对单独SynchronizationContext对象的需要。除非你有一定的互操作场景与COM +对象或WinForms的用户界面等共享代码,你最好的解决方案是使用Dispatcher替代SynchronizationContext。
This looks like:
这看起来像:
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());
}));
}
}
Note that you no longer need a TheUISync object - WPF handles that detail for you.
请注意,您不再需要 TheUISync 对象 - WPF 会为您处理该细节。
If you're more comfortable with the older delegatesyntax you can do it that way instead:
如果您对旧delegate语法更满意,则可以这样做:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(delegate
{
FooDoDoneEvent(this, new EventArgs());
}));
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(or Postin the original code) calls the delegate. The fix is a second test inside the delegate:
另请注意,此处复制的原始代码中有一个错误。问题是 FooDoneEvent 可以在 OnFooDoDone 被调用和BeginInvoke(或Post在原始代码中)调用委托的时间之间设置为 null 。修复是委托内部的第二个测试:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));
回答by Marc Gravell
Rather than compare to the current one, why not just let itworry about it; then it is simply a case of handling the "no context" case:
而不是比较当前的一个,为什么不干脆让它担心; 那么这只是处理“无上下文”情况的一个案例:
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);
}
}
}

