Reputation: 1652
I am implementing a thread that needs the following features:
My initial implementation of the message pump used GetMessage
like:
while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
The problem I found with that, is that GetMessage will never return unless there is a message.
Meaning, if there is low message activity, it may be quite a while before it checks Terminated
again.
My second implementation (inspired by this answer) used MsgWaitForMultipleObjects
to wait until a message exists before checking (since it has a timeout)
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;
The problem I've found with this, is that MsgWaitForMultipleObjects
blocks the thread while it waits. So, when a message is sent to the thread via SendMessageTimeout
, it times out, where it doesn't when using GetMessage
.
The solution that comes to mind is to go back to the GetMessage
implementation, but add a timer to make sure a WM_TIMER
message resets the loop every second.
Is this really the only way to do this? It seems there should be some better way to keep the thread responsive while waiting for messages.
Upvotes: 3
Views: 470
Reputation: 598134
My initial implementation of the message pump used GetMessage like:
while not Terminated and GetMessage(Msg, 0, 0, 0) do begin TranslateMessage(Msg); DispatchMessage(Msg); end;
The problem I found with that, is that GetMessage will never return unless there is a message. Meaning, if there is low message activity, it may be quite a while before it checks
Terminated
again.
You can override the thread's virtual TerminatedSet()
method to post a message to the queue via PostMessage()
or PostThreadMessage()
to "wake up" GetMessage()
if it is blocked.
Alternatively, have your thread constructor create a TEvent
object, and free it in the thread's destructor. Then have TerminatedSet()
signal that event. Your loop can then use MsgWaitForMultipleObjects()
to wait on the message queue AND the event at the same time. The return value will tell you whether the wait was satisfied by a message or the event.
My second implementation (inspired by this answer) used
MsgWaitForMultipleObjects
to wait until a message exists before checking (since it has a timeout)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;
The problem I've found with this, is that
MsgWaitForMultipleObjects
blocks the thread while it waits. So, when a message is sent to the thread viaSendMessageTimeout
, it times out, where it doesn't when usingGetMessage
.
The SendMessage...()
family of functions will deliver a message directly to the target window's message procedure, bypassing the message queue completely. So MsgWaitForMultipleObjects()
and (Get|Peek)Message()
will never report a sent message from SendMessage...()
, only a posted message from PostMessage()
or PostThreadMessage()
(or a synthesized message, like WM_TIMER
, WM_PAINT
, etc). However, when sending a message across thread boundaries, the receiving thread still needs to perform message retrieval calls (is, (Get|Peek)Message()
) in order for the sent message to actually be delivered to the window procedure.
The solution that comes to mind is to go back to the
GetMessage
implementation, but add a timer to make sure aWM_TIMER
message resets the loop every second.
Inside a thread, it would be better to use a waitable timer instead of WM_TIMER
, then you can use the timer with MsgWaitForMultipleObjects()
. But really, there is very little difference between using GetMessage()
with WM_TIMER
vs MsgWaitForMultipleObjects()
with a timeout, so there is no need to waste system resources creating the timer.
Upvotes: 5