Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to exit a thread's message loop?

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?

Background

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?


But don't do this

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;
like image 705
Ian Boyd Avatar asked May 04 '12 15:05

Ian Boyd


People also ask

How does the GetMessage() loop work?

The message loop calls GetMessage (), which looks in your message queue. If the message queue is empty your program basically stops and waits for one (it Blocks ).

How do I stop a thread from exiting before a message?

In the Send method, quit message is passed onto Post so all messages are dispatched before the tread is shutdown. To emulate the real world, Send waits until its message as been dispatched. The thread.Join (), in the Post method, is only used in this example to prevent the application from exiting before a running thread.

How do you suspend a thread for a message queue?

The thread relies upon a std::queue<ThreadMsg*> for the message queue. std::queue is not thread-safe so all access to the queue must be protected by mutex. A std::condition_variable is used to suspend the thread until notified that a new message has been added to the queue.

How does a thread handle incoming messages?

Incoming messages are dequeued by the thread and data is dispatched to an appropriate function handler based on a unique message identifier. Timer support capable of invoking a function is handy for low speed polling or to generate a timeout if something doesn’t happen in the expected amount of time.


1 Answers

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.

like image 87
Rob Kennedy Avatar answered Sep 30 '22 03:09

Rob Kennedy