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

Фоновый поток может быть настроен для получения оконных сообщений. Вы бы отправляли сообщения в ветку используя 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'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;

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

Мы уже узнали, что неправильный способ опубликовать 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 - небезопасно 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;

2 ответа

Решение

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

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

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

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

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

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

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

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

Другие вопросы по тегам