14

バックグラウンドスレッドは、ウィンドウメッセージを受信するように構成できます。を使用してスレッドにメッセージを投稿します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​​falseを返す正しい方法は何ですか。

メッセージを投稿する間違った方法は、次の電話をかけることであることWM_QUITをすでに学びました。

PostThreadMessage(nThreadId, WM_QUIT, 0, 0);

人々がこのアプローチを採用している例さえあります。MSDNから:

PostMessage関数を使用してWM_QUITメッセージを投稿しないでください。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;
4

2 に答える 2

7

正規の方法はありません。カノンはありません。GetMessageメッセージを投稿することでゼロを返すことができwm_Quit、それを行うには。を呼び出しPostQuitMessageます。いつ、どのようにそれを行うかはあなた次第です。

に応答してそれを行うwm_Destroyのが一般的ですが、それはプログラムを終了する一般的な方法がウィンドウを閉じることであるためです。閉じるウィンドウがない場合は、別の方法を選択してください。あなたのwm_PleaseEndYourself考えは大丈夫です。

メッセージを送信する必要もありません。イベントやセマフォなどの待機可能なオブジェクトを使用MsgWaitForMultipleObjectsして、新しいメッセージを待機しているときに、それが通知されたかどうかを検出するために使用できます。

GetMessageゼロが返されるのを待つ必要はありません。スレッドを停止する必要があることがすでにわかっている場合は、メッセージの処理を完全に停止できます。ループを終了する方法はたくさんあります。exit、、、、またはを使用することもbreak、ループ条件でチェックするフラグを戻り値とともに設定することもできます。raisegotoGetMessage

また、失敗すると-1が返されることにも注意してくださいGetMessage。これは、ゼロ以外の値としてtrueとして解釈されます。失敗した場合はおそらくメッセージループを続行したくないGetMessageので、代わりにを確認するGetMessage(...) > 0、ドキュメントで推奨されているようにしてください。

于 2012-05-04T16:10:57.370 に答える
6

使用PostThreadMessageは必ずしも正しくありません。あなたがリンクしたレイモンドの記事は言う:

システムは「悪い時間」にWM_QUITメッセージを挿入しようとしないためです。代わりに、WM_QUITメッセージを生成する前に「落ち着く」のを待つため、一連の投稿されたメッセージによってトリガーされるマルチステップ手順の途中でプログラムが実行される可能性が低くなります。

ここで概説されている懸念事項がメッセージキューに当てはまらない場合は、電話PostThreadMessageをかけてWM_QUIT自分をノックアウトしてください。PostQuitMessageそれ以外の場合は、スレッドから呼び出すことができる特別なシグナル、つまりユーザー定義のメッセージを作成する必要があります。

于 2012-05-04T16:11:50.053 に答える