我正在开发的当前应用程序是另一个商业应用程序的插件。在我的插件中,我可以使用当用户决定运行自定义命令时由父应用程序调用Command
的方法创建自定义类。Execute()
此 API 提供的对象不是线程安全的,因此我只能从Execute()
应用程序启动的线程访问它们。但是,我尝试执行的一些任务需要一段时间才能完成,我想向用户显示一个进度窗口。
我可以通过启动另一个线程来显示窗口来创建一个功能强大且响应迅速的进度窗口。这要求我手动启动此线程的 WPF 调度程序,如本博文中所述。
在此过程进行时,我可以拖动此窗口。但是,如果在任务运行时与窗口内的任何内容进行交互,例如单击窗口,它将冻结。
我发现如果我在实际Command.Execute()
线程中实例化并显示一个 WPF 窗口,则该窗口将在命令退出后保持可见和响应。因此,本机程序似乎在命令线程上运行了一个 WPF 调度程序。我通过连接 Dispatcher.CurrentDispatcher.Hooks.OperationPosted
from验证了这一点Execute()
。Execute()
当我与本机应用程序交互时,该方法退出后继续发送消息。
所以我认为正在发生的是当我单击窗口时,此单击正在由本机应用程序的 Dispatcher 处理。该调度程序不知道我的窗口,甚至无法访问它,因为它是在另一个线程中创建的。此外,该调度程序无论如何都不能做任何事情,因为它的关联线程正忙于我的Execute()
方法执行我的耗时任务。
那么有人可以解释如何单击或以其他方式与 WPF 对象交互最终添加到关联的调度程序队列中吗?有什么方法可以重定向它,以便当用户单击我的进度窗口时,事件会发送到正确的调度程序?
更新
这是我一直在使用的一些示例代码,试图弄清楚发生了什么。
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()
处理程序中设置的断点没有命中。
此时我可以设置一个断点,即使进度窗口被冻结,原始的“命令线程”仍在循环和工作。一旦完成,我会看到“工作完成”消息框,并且“命令线程”线程退出Command.Execute()
返回到本机应用程序。
这就是奇怪的地方。在“命令线程”退出到本机应用程序之后,我的断点button_OnClick()
被“进度窗口线程”击中。此外,就在那之后progWindow.UpdateStatus()
,我之前从“命令线程”排队的所有后续调用都会通过并将进度条一直更新到 100%
所以看来我最初的假设是错误的。该点击实际上是在正确的调度程序中注册的。但是,由于某种原因,“进度窗口线程”调度程序卡在此单击事件上,直到我的Execute()
方法退出并释放“命令线程”调度程序。
这有帮助吗?谁能解释发生了什么?我以为两个不同线程的调度程序是相互独立的?它的行为就好像“进度窗口线程”调度程序上的单击事件正在调用“命令线程”调度程序上的 Invoke() 并阻塞,直到该调度程序空闲。
我还应该提到,我无法在自己的独立 WPF 应用程序中重新创建此问题。如果我在 VS2012 中创建一个标准的 WPF 应用程序并在MainWindow
该调用上创建一个按钮,WorkerClass.DoWork()
它工作正常。单击按钮ProgressWindow
不会冻结窗口,并且button_OnClick
会立即调用处理程序。