Многопоточный цикл обработки сообщений Delphi

В моем приложении есть несколько потоков: 1) основной поток 2) 2 подосновных потока (каждый с циклом сообщений, как показано ниже), используемые TFQM 3) n рабочих потоков (простой цикл, содержащий Sleep())

Моя проблема в том, что когда я закрываю свое приложение, рабочие потоки удается правильно выйти, но 1 из 2 вспомогательных основных потоков зависает (никогда не выходит), когда я запускаю WM_QUIT, чтобы закрыть их.


procedure ThreadProcFQM(P: Integer); stdcall;
var
  Msg: TMsg;
 _FQM: TFQM;
begin
  _FQM := Ptr(P);
  try
    _FQM.fHandle := AllocateHwnd(_FQM.WndProc);

    while GetMessage(Msg, 0, 0, 0) do
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;

  finally
    DeallocateHWnd(_FQM.fHandle);
    SetEvent(_FQM.hTerminated);
  end;
end;

procedure TFQM.Stop;
begin
  PostMessage(fHandle, WM_QUIT, 0, 0);

  WaitForSingleObject(hTerminated, INFINITE);
  if hThread <> INVALID_HANDLE_VALUE then
  begin
    CloseHandle(hThread);
    hThread := INVALID_HANDLE_VALUE;
  end;
end;

person Atlas    schedule 23.10.2008    source источник


Ответы (3)


Если я могу указать на несколько проблем в вашем коде...

1) Вы не проверяете вывод AllocateHwnd. Да скорее всего никогда не подведёт, но всё же...

2) AllocateHwnd выходит из попытки .. наконец! В случае сбоя DeallocateHwnd вызывать не следует.

3) AllocateHwnd не является потокобезопасным. Если вы вызываете его из нескольких потоков одновременно, вы можете столкнуться с проблемами. Подробнее...

Как сказал Дэви, используйте MsgWaitForMultipleObjects вместо создания скрытого окна сообщений. Затем используйте PostThreadMessage для отправки сообщений в поток.

Если я могу разместить здесь плагин для совершенно бесплатного продукта, используйте мою OmniThreadLibrary. Гораздо проще, чем напрямую возиться с обменом сообщениями Windows.

person gabr    schedule 23.10.2008
comment
Д2007+, на самом деле. Тогда пора обновляться :) - person gabr; 23.10.2008
comment
Габр, ваша библиотека потоков выглядит фантастически - спасибо, что указали на нее. О, и я бы сказал, что ваш сайт спартанский, а не уродливый. - person Argalatyr; 24.10.2008

У меня была та же проблема, и я обнаружил, что не должен создавать скрытое окно только для получения сообщений. Темы уже имеют систему сообщений.

Я думаю, что вы создаете свой дескриптор Windows и сохраняете его в fHandle, но GetMessage проверяет цикл сообщений вашего потока. Поэтому сообщение PostMessage(fHandle, WM_QUIT, 0, 0); никогда не получает getmesssage.

Вы можете публиковать сообщения в своем потоке, используя PostThreadMessage, а в потоке вы используете GetMessage(CurrentMessage, 0, 0, 0). Единственное важное отличие состоит в том, что вы должны запустить цикл сообщений из своего потока, вызвав

PeekMessage(CurrentMessage, 0, WM_USER, WM_USER, PM_NOREMOVE);

Вы должны начать с этого, затем выполнить настройку и запустить цикл.

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

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

person Davy Landman    schedule 23.10.2008
comment
Ой, извините, я здесь впервые. Не знал о комментариях.... Попробую. Спасибо. - person Atlas; 23.10.2008
comment
Дэви прав: PeekMessage следует использовать для создания очереди сообщений. - person Remko; 05.10.2009
comment
Вы прочитали его на странице MSDN для PostThreadMessage. . Они даже рекомендуют, чтобы основной поток ждал события, которое поток устанавливает после создания сообщения. - person Ian Boyd; 04.05.2012

1) Вам не нужен AllocateHwnd в вашем потоке. Первый вызов GetMessage создаст отдельную очередь сообщений для этого потока. Но чтобы отправить сообщение в поток, вы должны использовать функцию PostThreadMessage.

Имейте в виду, что в момент вызова PostThreadMessage очередь еще не могла быть создана. Я обычно использую конструкцию:

while not PostThreadMessage(ThreadID, idStartMessage, 0, 0) do
  Sleep(1);

чтобы убедиться, что очередь сообщений создана.

2) Для завершения цикла потока я определяю свое собственное сообщение:

  idExitMessage = WM_USER + 777; // you are free to use your own constant here

3) Нет необходимости в отдельном событии, потому что вы можете передать дескриптор потока в функцию WaitForSingleObject. Итак, ваш код может выглядеть так:

  PostThreadMessage(ThreadID, idExitMessage, 0, 0);
  WaitForSingleObject(ThreadHandle, INFINITE);

Учтите, что ThreadID и ThreadHandle — разные значения.

4) Итак, ваш ThreadProc будет выглядеть так:

procedure ThreadProcFQM; stdcall;
var
  Msg: TMsg;
begin
  while GetMessage(Msg, 0, 0, 0) 
    and (Msg.Message <> idExitMessage) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end;
person Community    schedule 23.10.2008
comment
Как указано ниже, вы должны создать очередь сообщений с помощью PeekMessage: // Принудительное создание очереди сообщений PeekMessage(Msg, 0, WM_USER, WM_USER, PM_NOREMOVE); - person Remko; 05.10.2009