我正在开发的当前应用程序是另一个商业应用程序的插件。在我的插件中,我能够使用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
处理程序。