使用 SynchronizationContext 将事件发送回 WinForms 或 WPF 的 UI [英] Using SynchronizationContext for sending events back to the UI for WinForms or WPF

查看:23
本文介绍了使用 SynchronizationContext 将事件发送回 WinForms 或 WPF 的 UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 SynchronizationContext 将事件从执行大量多线程后台任务的 DLL 编组回 UI 线程.

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.

我知道单例模式不是我最喜欢的,但我现在使用它来在您创建 foo 的父对象时存储 UI 的 SynchronizationContext 的引用.

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());
            }
        }

    }
}

这在 WPF 中根本不起作用,TheUISync 实例 UI 同步(从主窗口提供)永远不会匹配当前的 SynchronizationContext.Current.在 windows 窗体中,当我做同样的事情时,它们将在调用后匹配,我们将返回到正确的线程.

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.

我讨厌的修复看起来像

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.

推荐答案

眼前的问题

您的直接问题是 SynchronizationContext.Current 不是为 WPF 自动设置的.要设置它,您需要在 WPF 下运行时在 TheUISync 代码中执行以下操作:

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:

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 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 one SynchronizationContext cannot really cross threads. There are a number of scenarios in which a SynchronizationContext can switch to a new thread - specifically anything which calls ExecutionContext.Run(). So if you are using SynchronizationContext 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.

如何绕过需要 SynchronizationContext

因此,我建议专门为此目的使用 WPF 的 Dispatcher 机制,即使使用 WinForms 代码也是如此.您已经创建了一个用于存储同步的TheUISync"单例类,因此很明显您可以通过某种方式连接到应用程序的顶层.不管你这样做,你可以添加代码来创建一些 WPF 内容到你的 WinForms 应用程序,以便 Dispatcher 可以工作,然后使用我在下面描述的新的 Dispatcher 机制.

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 that Dispatcher will work, then use the new Dispatcher mechanism which I describe below.

使用 Dispatcher 而不是 SynchronizationContext

WPF 的Dispatcher 机制实际上消除了对单独的SynchronizationContext 对象的需要.除非您有特定的互操作场景,例如与 COM+ 对象或 WinForms UI 共享代码,否则最好的解决方案是使用 Dispatcher 而不是 SynchronizationContext.

WPF's Dispatcher mechanism actually eliminates the need for a separate SynchronizationContext object. Unless you have certain interop scenarios such sharing code with COM+ objects or WinForms UIs, your best solution is to use Dispatcher instead of 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 会为您处理该细节.

Note that you no longer need a TheUISync object - WPF handles that detail for you.

如果您更喜欢旧的 delegate 语法,您可以这样做:

If you're more comfortable with the older delegate syntax you can do it that way instead:

      Application.Current.Dispatcher.BeginInvoke(
        DispatcherPriority.Normal, new Action(delegate
        {
          FooDoDoneEvent(this, new EventArgs()); 
        }));

一个不相关的错误需要修复

另请注意,这里复制的原始代码中有一个错误.问题是在调用 OnFooDoDone 和 BeginInvoke(或原始代码中的 Post)调用委托的时间之间,FooDoneEvent 可以设置为 null.修复是委托内部的第二个测试:

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 Post in the original code) calls the delegate. The fix is a second test inside the delegate:

    if(FooDoDoneEvent!=null)
      Application.Current.Dispatcher.BeginInvoke(
        DispatcherPriority.Normal, new Action(() =>
        {
          if(FooDoDoneEvent!=null)
            FooDoDoneEvent(this, new EventArgs()); 
        }));

这篇关于使用 SynchronizationContext 将事件发送回 WinForms 或 WPF 的 UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆