Windows(具体来说是Vista)如何确定我的应用程序是否挂起?

9

我有一个非常类似于这里描述的问题: https://microsoft.public.win32.programmer.kernel.narkive.com/Ly2P8Yp2/prevent-vista-from-marking-my-application-as-non-responding

那个线程建议任务管理器向进程发送WM_NULL消息,并期望进程在超时限制(5秒?)内消耗此消息。 当我搜索“WM_NULL hung”时,有许多引用相同的技术。

然而,在我的应用程序在进行漫长的操作时,我没有看到任何WM_NULL消息在队列中 - 我有一个辅助线程,每0.5秒切换到主线程并调用PeekMessage()查找WM_NULL,但它没有发现任何消息!

那么,Windows(Vista)用于确定应用程序是否挂起的方法是什么?

我的应用程序应该消耗哪些消息,以便Windows认为应用程序是响应的?

更多细节:

除了使用PeekMessage()查找WM_NULL,我们还会调用PeekMessage()来处理鼠标事件,因为我们还想了解用户是否选择了窗口中绘制了停止标志的某个区域。如果选择了该区域,我们会设置一个标志,主线程中的冗长操作会定期检查该标志,并在选择了停止标志时停止。 Vista存在的问题是,当它将应用程序声明为无响应时,它会用幽灵窗口替换其窗口 - 请参见description of PeekMessage()
“如果顶层窗口在几秒钟内停止响应消息,系统认为该窗口无响应,并用具有相同z顺序、位置、大小和视觉属性的幽灵窗口替换它。这使得用户可以移动它、调整它的大小或甚至关闭应用程序。但是,这些都是唯一可用的操作,因为应用程序实际上没有响应。当应用程序正在进行调试时,系统不会生成幽灵窗口。”
这个幽灵窗口不允许鼠标选择通过到达我们的窗口,因为窗口不再显示在屏幕上! 因此,我的目标是防止这个幽灵窗口首先出现...
经过进一步调查后:

在我添加了Michael在回答这个问题时建议的代码之后

while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

应用程序现在不再被Windows视为挂起状态;但是我无法使用此解决方案,因为应用程序开始对各种按钮等做出反应(这不应该发生)。 因此,我尝试查看哪些消息正在到达。我使用了Spy++和调试打印,两者都只显示了两种类型的消息:WM_TIMER和0x0118(WM_SYSTIMER)。因此,我修改了代码如下:

 while (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE) ||
        PeekMessage(&msg, NULL, 0x0118, 0x0118, PM_REMOVE))
 {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
 }

令人惊讶的是,应用程序又卡住了!!

现在我真的陷入困境了。如果我截获所有进来的消息,并让应用程序处理它们,为什么Windows还认为应用程序没有处理事件?

非常感谢任何有意义的建议。


1
你在UI线程上做什么?你考虑过尝试将阻塞操作移动到后台线程,而不是试图逆向工程窗口管理器吗? - i_am_jorf
1
UI线程正在忙于计算某些内容。不幸的是,像许多庞大的应用程序一样,使大量旧代码多线程化并不是一项简单的任务... :( - user103869
更新了我的回答。一定要清空所有消息,以确保您不会被幽灵化。 - Michael
我相信现在我已经处理了所有的消息。循环看起来与您的示例完全相同。但是Vista仍然干扰我的窗口... - user103869
2
通过过滤消息,你在告诉操作系统你不关心输入事件,这样它有可能会将你标记为挂起状态。如果你不希望输入事件传递到窗口的其他控件中,你应该泵送所有的消息,并禁用这些控件。创建一个模态进度 UI 对话框可能是实现此目的最简单的方法。 - Michael
2个回答

4

TaskManager可能使用IsHungAppWindow来确定应用程序是否挂起。根据MSDN的说法,如果应用程序不等待输入、不在启动处理中或者在5秒内没有处理消息,则认为应用程序已经挂起。因此不需要WM_NULL。

您不需要消耗任何特定的消息-只需定期抽取消息并将长时间任务移出UI线程即可。如果您可以使PeekMessage每0.5秒调用一次,则可以将其替换为以下内容:

while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

这将完全排空您的消息队列,并使您对用户更响应。不要过滤单个消息,如鼠标消息。如果可能的话,您应该每隔0.5秒以上执行一次,长期来看,尝试将长时间工作移出UI线程。

正如我所提到的,我的应用程序每0.5秒调用PeekMessage()函数,因此IsHungAppWindow()函数不应将其报告为挂起状态... - user103869
你是指定PM_REMOVE,还是给一个特定的WM_NULL过滤器?我认为MSDN在这里误导了,你需要实际获取队列中的输入消息。 - Michael
好的,这里肯定有更多的问题,因为泵送消息应该足以使应用程序响应。只要队列中有消息,您就会在循环中执行此操作,对吗?您的出现是否看起来对您很敏感?还是看起来挂起了——您无法移动它,无法绘制等等? - Michael
2
我已经附上了Spy++,它显示没有任何消息(甚至是WM_NULL)被发送到窗口(或来自同一进程/线程的其他窗口)。我看到的最后一条消息是WM_PAINT,在应用程序计算数字时不会出现新消息,最后当窗口被替换为幽灵窗口时,会出现更多消息。这让我回到了最初的问题:如果不是通过发送WM_NULL,Windows如何确定我的应用程序已经挂起? - user103869
我在帖子中添加了更多我的调查细节 - 请看那里。 - user103869
显示剩余3条评论

0

解决方案是在分派消息后再进行一次额外的调用。


// check for my messages
while (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE) ||
        PeekMessage(&msg, NULL, 0x0118, 0x0118, PM_REMOVE))
 {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
 }

// only to prevent ghost-window on vista!
// we dont use the result and let the message in the queue (PM_NOREMOVE)
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);


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