问题是有两个用于发布消息的消息队列。这样做的结果是your发布的消息是总是在任何之前处理Paint, Input, or Timer消息。 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943%28v=vs.85%29.aspx
这意味着您正在用数十万条消息淹没消息队列。这些消息始终会在绘制和用户消息之前得到处理 - 使您的应用程序看起来挂起。
解决这个问题的常用方法是使用定时器;让您的代码启动一个持续时间很短(例如 0 毫秒)的计时器。
澄清
定时器消息(WM_TIMER
),如画图消息(WM_PAINT
)和输入消息(例如 WM_MOUSEMOVE、WM_KEYDOWN)被处理after发布消息。定时器消息专门在输入和绘制消息之后进行处理。
这意味着您的应用程序将响应用户事件和绘制请求before它处理下一个WM_TIMER
信息。您可以通过使用计时器(它是WM_TIMER
消息),而不是自己发布消息(这始终优先于绘制和输入消息)。
当定时器触发时,他们会发送一个WM_TIMER
信息。该消息将始终被处理after任何输入和绘制消息;使您的应用程序看起来响应迅速。
设置计时器的另一个好处是,它强制您将已处理的所有项目“排队”到(线程安全)列表中。您可以将数千个工作项合并到一个“计时器”中,从而使系统不必生成和处理数千条不必要的消息。
奖金喋喋不休
...消息按以下顺序处理:
- 发送信息
- 发布的消息
- 输入(硬件)消息和系统内部事件
- 已发送消息(再次)
-
WM_PAINT http://msdn.microsoft.com/en-us/library/windows/desktop/dd145213%28v=vs.85%29.aspx消息
-
WM_TIMER http://msdn.microsoft.com/en-us/library/windows/desktop/ms644902%28v=vs.85%29.aspx消息
Update: 你应该not有一个计时器轮询,这只是浪费而且是错误的。你的线程应该“设置一个标志“(即计时器)。这允许您的主线程实际上处于空闲状态,仅在有事情要做时才唤醒。
更新二:
演示消息生成顺序未保留的伪代码:
//Code is public domain. No attribution required.
const
WM_ProcessNextItem = WM_APP+3;
procedure WindowProc(var Message: TMessage)
begin
case Message.Msg of
WM_Paint: PaintControl(g_dc);
WM_ProcessNextItem:
begin
ProcessNextItem();
Self.Invalidate; //Invalidate ourselves to trigger a wm_paint
//Post a message to ourselves so that we process the next
//item after any paints and mouse/keyboard/close/quit messages
//have been handled
PostMessage(g_dc, WM_ProcessNextItem, 0, 0);
end;
else
DefWindowProc(g_dc, Message.Msg, Message.wParam, Message.lParam);
end;
end;
即使我生成了WM_ProcessNextItem
after我生成一个WM_PAINT
消息(即Invalidate
), the WM_PAINT
永远不会被处理,因为有always另一个人在它之前发布了消息。和正如 MSDN 所说 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943%28v=vs.85%29.aspx,只有在没有其他发布的消息时才会出现绘制消息。
更新三:是的,只有消息队列,但这就是我们不关心的原因:
已发送和发布的消息
我在这里使用的术语是非标准的,但我使用它是因为我认为它比标准术语更清晰一些。出于本次讨论的目的,我将说与线程关联的消息分为三个桶,而不是更标准的两个:
What I'll call them Standard terminology
=========================== =============================
Incoming sent messages Non-queued messages
Posted messages \_
Input messages / Queued messages
实际上,消息分解比这更复杂,但我们现在将坚持上述模型,因为它“足够真实”。
旧事新事,Windows 演进过程中的实际开发
作者:雷蒙德·陈
国际标准书号 0-321-44030-7
版权所有 © 2007 培生教育公司
第 15 章 - 如何传递和检索窗口消息,第 358 页
更容易想象有two消息队列。在“第一个”队列为空之前,不会读取“第二个”队列中的消息;并且OP永远不会让第一个队列耗尽。因此,第二个队列中的“paint”和“input”消息都没有得到处理,使得应用程序看起来挂起。
这是实际情况的简化,但对于本次讨论的目的来说已经足够接近了。
更新四
问题不一定是您用消息“淹没”了输入队列。您的应用程序可能会无响应,仅one信息。只要你有one在队列中发布消息,它将在任何其他消息之前被处理。
想象一下发生了一系列事件:
- 鼠标移动(
WM_MOUSEMOVE
)
- 鼠标左键被按下(
WM_LBUTTONDOWN
)
- 释放鼠标左键(
WM_LBUTTONUP
)
- 用户将另一个窗口移开,导致您的应用程序需要重新绘制(
WM_PAINT
)
- 您的线程已准备好一个项目,并发布通知 (
WM_ProcessNextItem
)
您的应用程序的主消息循环 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644928%28v=vs.85%29.aspx(这称为GetMessage
)将不会按照消息发生的顺序接收消息。它将检索WM_ProcessNextItem
信息。这将从队列中删除消息,留下:
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
当您处理项目时,用户会进一步移动鼠标,然后随机单击:
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
为了回应您的WM_ProcessNextItem
您又给自己发了一条消息。您这样做是因为您希望先处理未完成的消息,然后再继续处理更多项目。这会将另一条发布的消息添加到队列中:
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_ProcessNextItem
问题开始变得明显。下一个呼叫GetMessage
将检索WM_ProcessNextItem
,使应用程序积压绘画和输入消息:
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
解决方案是利用消息的无序处理。发布的消息始终在绘制/输入/计时器消息之前处理:不要使用发布的消息。你可以think消息队列分为两组:发布消息和输入消息。而不是导致“已发布”消息队列永远不允许为空的情况:
Posted messages Input messages
================== =====================
WM_ProcessNextItem WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
你可以使用一个WM_TIMER
信息:
Posted messages Input messages
================== =====================
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
WM_TIMER
挑剔角:这个关于两个队列的描述严格来说并不正确,但它足够真实。 Windows 如何按照记录的顺序传递消息是内部实现细节,可能随时更改。
重要的是要注意,你不是polling如果有计时器,那就糟糕了™。相反,你是firing一次性计时器作为一种机制来生成WM_TIMER
信息。你这样做是因为你知道timer消息不会优先于paint or input消息。
使用计时器还有另一个可用性优势。之间WM_PAINT
, WM_TIMER
, and input消息,也有乱序处理:
-
input消息,然后
-
WM_PAINT
, then
WM_TIMER
如果您使用计时器来通知主线程,还可以保证您将更快地处理绘制和用户输入。这可确保您的应用程序保持响应。这是可用性的增强,并且您可以免费获得。