由于Dispatcher.BeginInvoke()的行为让人感到困惑

12

有人能够帮我解决一个问题吗?

我正在开发一个WPF项目,情景如下:

我需要弹出一个模态窗口在主UI线程上,然后关闭它。这些操作是从另一个UI线程开始的,以防止用户点击主UI窗口。然后我关闭这个窗口。主要代码如下所示。而且它可以工作。

据我所知,在没有使用调度程序的代码(即在UI线程上),ShowDialog() 方法在返回之前将不会被执行,是否有人对多线程有经验?

   Window window;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(() =>
           {


              //create a window and let user work from this thread

             //code is omitted.



               //create another window on main UI thread

              Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    window = new Window();
                    window.ShowDialog();
                }));



               //do some work here

               Thread.Sleep(1000);

               Application.Current.Dispatcher.BeginInvoke(new Action(() =>
               {
                   //Thread.Sleep(1000);
                   window.Close();
               }));
           });

        thread.Start();
    }

感谢您的时间!

2个回答

24

如果我正确理解了你的问题,你是在说这段代码正好按照你想要的方式工作,但你只是想了解它是如何(以及为什么)工作的?

这就是它的工作原理。首先,你的线程运行这段代码:

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
    window = new Window();
    window.ShowDialog();
}));

这会将你的操作排队到主(UI)线程的调度程序队列中,然后立即返回:你的工作线程继续运行。

当应用程序首次启动时(通常是通过初始化App.xaml对象的编译器生成的代码启动,但也可以通过调用Application.Run显式执行),它会启动消息循环,大致过程如下(伪代码,非常简化):

public class Application {
    public void Run() {
        while (!Exited && action = Dispatcher.DequeueAction())
            action();
    }
}

因此,在您排队操作后不久,UI线程将开始处理您的操作并运行它,此时您的操作将创建一个窗口并以模态方式显示。

模态窗口现在启动了自己的消息循环,大致如下(同样是非常简化的):

public class Window {
    public bool? ShowDialog() {
        DisableOtherWindowsAndShow();
        while (!IsClosed && action = Dispatcher.DequeueAction())
            action();
        EnableOtherWindowsAndHide();
        return DialogResult;
    }
}

稍后,您的工作线程运行以下代码:

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
    window.Close();
}));

再次强调,您的操作被排队到UI线程的分派程序队列中,然后BeginInvoke调用立即返回,您的工作线程继续运行。

因此,不管是早些时候还是晚些时候,UI线程的消息循环都会开始取消排队并执行您的操作,该操作告诉窗口要关闭。这与用户点击标题栏的“X”按钮具有基本相同的效果,在模式对话框内进行此操作当然也是完全可以的。这将导致ShowDialog的消息循环终止(因为窗口现在已关闭),在此时对话框被隐藏并且其他窗口重新启用,ShowDialog返回,您原始的(ShowDialog)操作完成并返回,控制权回到Application.Run中的原始消息循环。

请注意,每个线程都有一个分派程序队列,而不是每个消息循环都有一个。因此,您的“关闭”操作进入与您的“显示对话框”操作相同的队列。现在执行消息循环轮询的是不同的代码(位于ShowDialog内部而不是Application.Run内),但循环的基本原则是相同的。


谢谢你的解释,它真的帮助我理解了它的工作原理!我实际上看了showdialog代码,但有些东西很难理解,比如ComponentDispatcher.PushModal,我在下面发布了它们,请你帮我看一下吗? - xiaoheixiaojie

4

BeginInvoke 是一个非阻塞方法;它将操作添加到调度程序队列中,而不等待其完成。您应该使用 Invoke,它在调度程序线程上同步调用方法。


你好,感谢您的回复。恐怕这不是我需要的答案。在第一个BeginInvoke之后,我需要做一些工作,这会禁用主UI上的窗口。所以如果我使用Invoke,应用程序将被阻塞。目前这种行为并没有引起任何问题。 - xiaoheixiaojie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接