Подтвердить что ты не робот

Как выйти из цикла сообщений потока?

Фоновый поток можно настроить для приема оконных сообщений. Вы отправляете сообщения в поток, используя PostThreadMessage. Каков правильный способ выхода из этого цикла сообщений?

Фон

Прежде чем отправлять сообщения в фоновый поток, поток должен гарантировать, что очередь сообщений создается, вызывая 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;

Теперь внешний мир может отправлять сообщения в наш поток:

PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);

и наш поток находится в цикле GetMessage:

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 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 no window associated.
      // Dispatch will just throw the message away
//    else
//       TranslateMessage(Msg);
//       DispatchMessage(Msg);
//    end;
   end;
end;

Мой вопрос в том, что правильный способ иметь GetMessage получить сообщение WM_QUIT и вернуть false.

Мы уже узнали , что неправильный способ отправки сообщения WM_QUIT должен вызвать:

PostThreadMessage(nThreadId, WM_QUIT, 0, 0);

Есть даже примеры, где люди используют этот подход. Из MSDN:

Не отправляйте сообщение WM_QUIT с помощью функции PostMessage; используйте PostQuitMessage.

Правильный способ "кого-то" вызвать PostQuitMessage. PostQuitMessage - это специальная функция, которая устанавливает специальный флаг, связанный с очередью сообщений, так что GetMessage будет синтезировать сообщение WM_QUIT когда время будет правильным.

Если это был цикл сообщений, связанный с "окном", тогда стандартный шаблон проектирования - это когда окно разрушается, и получено сообщение WM_DESTROY, мы его поймаем и вызываем 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;

Проблема заключается в том, что диспетчер окон отправляет WM_DESTROY . Он отправляется, когда кто-то звонит DestroyWindow. Неправильно просто отправить WM_DESTROY.

Теперь я могу синтезировать какое-то искусственное сообщение WM_PleaseEndYourself:

PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);

а затем обработайте его в моей цепочке сообщений потока:

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;

Но существует ли канонический способ выхода из цикла сообщений потока?


Но не делайте этого

Оказывается, для всех лучше, чтобы вы не использовали очередь сообщений без окон. Многое может быть непреднамеренно и тонко, сломанно, если у вас нет окна для отправки сообщений.

Вместо этого выделите скрытое окно (например, используя Delphi thread - небезопасно AllocateHwnd) и отправлять сообщения на него, используя простой старый 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;

Если у нас есть простая процедура старого окна для обработки сообщений:

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;    

и когда вы хотите, чтобы поток завершился, вы скажете:

procedure TMyThread.Terminate;
begin
   PostMessage(Fhwnd, WM_TerminateYourself, 0, 0);
end;
4b9b3361

Ответ 1

Нет канонического пути; нет канона. Вы получаете GetMessage для возврата нуля, отправляя сообщение wm_Quit, и вы делаете это, вызывая PostQuitMessage. Когда и как вы это знаете, это зависит от вас.

Обычно это делается в ответ на wm_Destroy, но это только потому, что общий способ выхода из программы - закрытие окон. Если у вас нет окна для закрытия, выберите другой способ. Идея wm_PleaseEndYourself в порядке.

Вам даже не нужно отправлять сообщение. Вы можете использовать некоторый ожидаемый объект, например событие или семафор, а затем использовать MsgWaitForMultipleObjects для определения того, сигнализировали ли он, пока ожидаете новых сообщений.

Вам даже не нужно ждать, пока GetMessage вернет нуль. Если вы уже знаете, что поток необходимо остановить, вы можете просто полностью прекратить обработку сообщений. Существует множество способов выхода из цикла. Вы можете использовать exit, break, raise или даже goto, или вы можете установить флаг, который вы проверяете в состоянии цикла вместе с возвращаемым значением GetMessage.

Обратите также внимание, что GetMessage возвращает -1 при сбое, который в качестве ненулевого значения будет интерпретироваться как истинный. Вероятно, вы не хотите продолжать цикл сообщений, если GetMessage терпит неудачу, поэтому вместо проверки GetMessage(...) > 0 или выполните документация рекомендует.

Ответ 2

Использование PostThreadMessage не обязательно неверно. Статья Раймонда, с которой вы связались, гласит:

Поскольку система пытается не вводить сообщение WM_QUIT в "плохое время"; вместо этого он ждет, чтобы вещи "успокоились" перед генерированием сообщения WM_QUIT, тем самым уменьшив вероятность того, что программа может находиться в середине многоступенчатой ​​процедуры, вызванной последовательностью отправленных сообщений.

Если проблемы, изложенные здесь, не относятся к вашей очереди сообщений, вызовите PostThreadMessage с помощью WM_QUIT и выбейте себя. В противном случае вам нужно создать специальный сигнал, то есть пользовательское сообщение, которое позволяет вам вызывать PostQuitMessage из потока.