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