A background-thread can be configured to receive window messages. You would post messages to the thread using PostThreadMessage
. What's the correct way to exit that message loop?
Before you can post messages to a background thread, the thread needs to ensure that a message queue is created by calling PeekMessage
:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
end;
Now the outside world is able to post messages to our thread:
PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);
and our thread sits in a GetMessage
loop:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
// GetMessage can return -1 if there's an error
// Delphi LongBool interprets non-zero as true.
// If GetMessage *does* fail, then msg will not be valid.
// We want some way to handle that.
//Invalid:
//while (GetMessage(msg, 0, 0, 0)) do
//Better:
while LongInt(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
// No point in calling Translate/Dispatch if there's no window associated.
// Dispatch will just throw the message away
// else
// TranslateMessage(Msg);
// DispatchMessage(Msg);
// end;
end;
end;
My question is what's the correct way to have GetMessage
receive a WM_QUIT
message and return false.
We've already learned that the incorrect way to post a WM_QUIT
message is to call:
PostThreadMessage(nThreadId, WM_QUIT, 0, 0);
There is even examples out there where people employ this approach. From MSDN:
Do not post the WM_QUIT message using the PostMessage function; use PostQuitMessage.
The correct way is for "someone" to call PostQuitMessage
. PostQuitMessage
is a special function, that sets the special flag associated with a message queue so that GetMessage
will synthesize a WM_QUIT
message when the time is right.
If this were a message loop associated with a "window", then the standard design pattern is when the window is being destroyed, and a WM_DESTROY
message is received, we catch it and call PostQuitMessage
:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_DESTROY: PostQuitMessage(0);
end;
end;
end;
Problem is that WM_DESTROY
is sent by the Window Manager. It is sent when someone calls DestroyWindow
. It is wrong to just post WM_DESTROY
.
Now i could synthesize some artifical WM_PleaseEndYourself
message:
PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);
and then handle it in my thread's message loop:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_PleaseEndYourself: PostQuitMessage(0);
end;
end;
end;
But is there a canonical way to exit a thread's message loop?
It turns out that it's better for everyone that you don't use a windowless message queue. A lot of things can be unintentionally, and subtly, broken if you don't have a window for messages to be dispatched to.
Instead allocate hidden window (e.g. using Delphi's thread-unsafe AllocateHwnd
) and post messages to it using plain old PostMessage
:
procedure TMyThread.Execute;
var
msg: TMsg;
begin
Fhwnd := AllocateHwnd(WindowProc);
if Fhwnd = 0 then Exit;
try
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
finally
DeallocateHwnd(Fhwnd);
Fhwnd := 0;
end;
end;
Where we can have a plain old window procedure to handle the messages:
WM_TerminateYourself = WM_APP + 1;
procedure TMyThread.WindowProc(var msg: TMessage);
begin
case msg.Msg of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_TerminateYourself: PostQuitMessage(0);
else
msg.Result := DefWindowProc(Fhwnd, msg.msg, msg.wParam, msg.lParam);
end;
end;
and when you want the thread to finish, you tell it:
procedure TMyThread.Terminate;
begin
PostMessage(Fhwnd, WM_TerminateYourself, 0, 0);
end;
There is no canonical way; there is no canon. You get GetMessage
to return zero by posting a wm_Quit
message, and you do that by calling PostQuitMessage
. When and how you know to do that is up to you.
It common to do it in response to wm_Destroy
, but that's only because the common way to exit programs is by closing windows. If you have no window to close, then choose a different way. Your wm_PleaseEndYourself
idea is fine.
You don't even have to send a message. You could use some waitable object like an event or a semaphore, and then use MsgWaitForMultipleObjects
to detect whether it's signaled while also waiting for new messages.
You don't really even have to wait for GetMessage
to return zero. If you already know the thread needs to stop, then you can simply stop processing messages entirely. There are lots of ways to exit a loop. You could use exit
, break
, raise
, or even goto
, or you could set a flag that you check in the loop condition along with the return value of GetMessage
.
Note also that GetMessage
returns -1 on failure, which as a non-zero value will be interpreted as true. You probably don't want to continue your message loop if GetMessage
fails, so instead check for GetMessage(...) > 0
, or do like the documentation recommends.