我正在实现一个需要以下功能的线程:
及时响应终止请求
泵消息
在等待消息时保持对SendMessage请求的响应
我最初对消息泵的实现使用GetMessage
像这样:
while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
我发现的问题是,除非有消息,否则GetMessage将永远不会返回。
这意味着,如果消息活动较少,则可能需要一段时间才能再次检查
Terminated
。我的第二个实现(受this answer启发)使用
MsgWaitForMultipleObjects
等到消息存在之前再进行检查(因为它有超时)while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLEVENTS) = WAIT_OBJECT_0 then
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;
我发现的问题是
MsgWaitForMultipleObjects
在等待时阻塞了线程。因此,当消息通过SendMessageTimeout
发送到线程时,它会超时,而在使用GetMessage
时不会。想到的解决方案是回到
GetMessage
实现,但是添加一个计时器以确保WM_TIMER
消息每秒重置一次循环。这真的是唯一的方法吗?
似乎应该有一些更好的方法来使线程在等待消息时保持响应。
最佳答案
消息泵的最初实现使用GetMessage像这样:
while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
我发现的问题是,除非有消息,否则GetMessage将永远不会返回。这意味着,如果消息活动较少,则可能需要一段时间才能再次检查
Terminated
。您可以重写线程的虚拟
TerminatedSet()
方法,以通过PostMessage()
或PostThreadMessage()
将消息发布到队列中以“唤醒” GetMessage()
(如果被阻止)。或者,让您的线程构造函数创建一个
TEvent
对象,并将其释放到线程的析构函数中。然后让TerminatedSet()
发出该事件信号。然后,循环可以使用MsgWaitForMultipleObjects()
同时等待消息队列和事件。返回值将告诉您等待是通过消息还是事件完成的。我的第二个实现(受this answer启发)使用
MsgWaitForMultipleObjects
等到消息存在之前再进行检查(因为它有超时)while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLEVENTS) = WAIT_OBJECT_0 then
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;
我发现的问题是
MsgWaitForMultipleObjects
在等待时阻塞了线程。因此,当消息通过SendMessageTimeout
发送到线程时,它会超时,而在使用GetMessage
时不会。SendMessage...()
系列函数将消息直接传递到目标窗口的消息过程,而完全绕过消息队列。因此,MsgWaitForMultipleObjects()
和(Get|Peek)Message()
将永远不会报告来自SendMessage...()
的已发送消息,而只会报告来自PostMessage()
或PostThreadMessage()
的已发布消息(或综合消息,例如WM_TIMER
,WM_PAINT
等)。但是,在跨线程边界发送消息时,接收线程仍然需要执行消息检索调用((Get|Peek)Message()
),以便将发送的消息实际传递到窗口过程。想到的解决方案是回到
GetMessage
实现,但是添加一个计时器以确保WM_TIMER
消息每秒重置一次循环。在线程内,最好使用waitable timer而不是
WM_TIMER
,然后可以将计时器与MsgWaitForMultipleObjects()
一起使用。但是实际上,将GetMessage()
与WM_TIMER
与MsgWaitForMultipleObjects()
一起使用在超时之间几乎没有区别,因此无需浪费系统资源来创建计时器。