UI事件如何进入WPF Dispatcher?

时间:2013-09-05 01:35:07

标签: c# .net wpf multithreading dispatcher

我正在开发的当前应用程序是另一个商业应用程序的插件。在我的插件中,我能够使用Command方法创建自定义Execute()类,当用户决定运行自定义命令时,该方法由父应用程序调用。

此API提供的对象不是线程安全的,因此我只能从应用程序启动的Execute()线程访问它们。但是,我正在尝试执行的一些任务需要一段时间才能完成,我想向用户显示一个进度窗口。

我可以通过启动另一个线程来显示窗口,从而创建一个正常运行且响应迅速的进度窗口。这要求我手动启动此线程的WPF调度程序,如this blog post中所述。

我可以在流程进行过程中拖动此窗口。但是,如果在任务运行时与窗口内的任何内容进行交互,例如单击窗口,它将会冻结。

我发现如果我在实际的Command.Execute()线程中实例化并显示一个WPF窗口,那么在命令退出后窗口将保持可见和响应。因此,本机程序似乎在命令线程上运行了WPF调度程序。我通过Dispatcher.CurrentDispatcher.Hooks.OperationPosted挂钩Execute()来验证这一点。在我与本机应用程序交互时退出Execute()方法后,这继续发送消息。

所以我认为当我点击窗口时,本机应用程序的Dispatcher正在处理此点击。这个调度程序不知道我的窗口,甚至可以访问它,因为它是在另一个线程中创建的。此外,该调度程序无论如何都无法执行任何操作,因为它的关联线程在执行我耗时任务的Execute()方法中很忙。

那么有人可以解释一下如何点击或以其他方式与WPF对象进行交互最终会被添加到关联的Dispatcher队列中吗?有什么方法可以重定向这个,这样当用户点击我的进度窗口时,事件会被发送到正确的调度员吗?

更新

以下是我一直在尝试弄清楚发生了什么的一些示例代码。

当用户唤起我的自定义命令时,本机应用程序会调用

Command.Execute()。 ProgressWindow只是一个带有ProgressBar和按钮的简单窗口。该按钮只有一个空的onClick处理程序,我设置了一个断点。

WorkerClass.DoWork()在另一个帖子上打开ProgressWindow。然后它通过循环for循环来做一些假工作,在每次迭代时更新窗口中的进度条。

public class Command
{
    public void Execute()
    {
        Thread.CurrentThread.Name = "Command Thread";
        WorkerClass.DoWork();
    }
}

public class WorkerClass
{
    //Creates the new progress window thread
    public static void DoWork()
    {
        Thread newprogWindowThread = new Thread(new ThreadStart(ShowProgressWindow));
        newprogWindowThread.Name = "Progress Window Thread";
        newprogWindowThread.SetApartmentState(ApartmentState.STA);
        newprogWindowThread.IsBackground = true;

        //Starts New Progress Window Thread
        using (_progressWindowWaitHandle = new AutoResetEvent(false))
        {
            //Starts the progress window thread
            newprogWindowThread.Start();

            //Wait for progress window thread to notify that the window is up
            _progressWindowWaitHandle.WaitOne();
        }

        //Do some fake work
        for (int i = 1; i <= 100; i++)
        {
            //Do Some work
            Thread.Sleep(100);

            //Updates the progress window
            //This method queues the update to the 
            //actual Dispatcher of the window.
            progWindow.UpdateStatus("Item " + i.ToString(), i, 100);
        }


            MessageBox.Show("Work Finished");

    }

    private static ProgressWindow progWindow;
    internal static EventWaitHandle _progressWindowWaitHandle;

    private static void ShowProgressWindow()
    {
        //Creates and shows progress window
        progWindow = new ProgressWindow("Running Task...");
        progWindow.Show();

        //Subscribes to window closed event
        progWindow.Closed += progWindow_Closed;

        //Notifies other thread the progress window is open when the dispatcher starts up
        System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Func<bool>(_progressWindowWaitHandle.Set));

        //Starts the WPF dispatcher
        System.Windows.Threading.Dispatcher.Run();
    }

    private static void progWindow_Closed(object sender, EventArgs e)
    {
        //When window is closed shuts down WPF dispatcher
        //this will release thread from Dispatcher.Run() above
        //and exit the ShowProgressWindow() method and exit the secondary thread
        ProgressWindow window = (ProgressWindow)sender;
        window.Dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
    }
}

就像我之前说的,如果我让它运行它可以正常工作。 “工作”完成后,进度条会正确更新。

然而,如果我单击进度窗口上的按钮,它将冻结。另外,我在button_OnClick()处理程序中设置的断点没有被击中。

此时我可以设置一个断点,看看原始的“命令线程”仍在循环并且即使进度窗口被冻结也能正常工作。完成后,我看到“Work Finished”消息框,“Command Thread”线程退出Command.Execute()回到本机应用程序。

所以这就是奇怪的。在“命令线程”退出到本机应用程序之后,button_OnClick()中的断点被“进度窗口线程”命中。此外,在此之后,我之前从“命令线程”排队的所有后续progWindow.UpdateStatus()调用都会通过并将进度条一直更新为100%

所以看来我原来的假设是错误的。实际上,点击是在正确的调度员中注册的。但是,出于某种原因,“进度窗口线程”调度程序卡在此单击事件上,直到我的Execute()方法退出并释放“命令线程”调度程序。

这有帮助吗?谁能解释一下发生了什么?我认为两个不同线程的Dispatchers彼此独立?它的行为就像“Progress Window Thread”调度程序上的click事件正在“Command Thread”调度程序上调用Invoke()并阻塞,直到该调度程序空闲为止。

我还应该提一下,我无法在自己的独立WPF应用程序中重新创建此问题。如果我在VS2012中制作标准WPF应用程序并在MainWindow上创建一个调用WorkerClass.DoWork()的按钮,则它可以正常工作。单击ProgressWindow上的按钮不会冻结窗口,并立即调用button_OnClick处理程序。

0 个答案:

没有答案