6

TThreadインスタンスの作成から起動までの間、メイン スレッドはコードの実行を続行します。メインスレッドのコードが問題のスレッドに依存して完全に稼働している場合、スレッドExecuteメソッドが実際に開始されるまで何らかの形で待機する必要があります。

次のコードを検討してください。

const
  WM_MY_ACTION = WM_APP + 10;

type
  TWndThread = class(TThread)
  protected
    fWndHandle: THandle;
    IsRunning: boolean;
    procedure WndProc(var Msg: TMessage);
    procedure Execute; override;
  public
    Test: integer;
    procedure AfterConstruction; override;
    procedure DoAction;
  end;

procedure TWndThread.AfterConstruction;
begin
  inherited;
  while not IsRunning do Sleep(100); // wait for thread start up
end;

procedure TWndThread.Execute;
var
  Msg: TMsg;
begin
  fWndHandle := AllocateHWnd(WndProc);
  IsRunning := true;
  try
    while not Terminated do
      begin
        if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
          begin
            while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
              begin
                TranslateMessage(Msg);
                DispatchMessage(Msg);
              end;
          end;
      end;
  finally
    DeallocateHWnd(fWndHandle);
  end;
end;

procedure TWndThread.WndProc(var Msg: TMessage);
begin
  case Msg.Msg of
    WM_MY_ACTION:
      begin
        inc(Test);
      end;
    else Msg.Result := DefWindowProc(fWndHandle, Msg.Msg, Msg.WParam, Msg.LParam);
  end;
end;

procedure TWndThread.DoAction;
begin
  PostMessage(fWndHandle, WM_MY_ACTION, 0, 0);
end;

var
  t: TWndThread;
begin
  t := TWndThread.Create;
  t.DoAction;
  t.Terminate;
end;

IsRunningフラグを待機するループDoActionがないと、含まれているウィンドウ ハンドルがまだ作成されないため、メッセージを正常に投稿できません。基本的にinc(Test)内部WndProcは発動しません。

スレッドの起動を待ってExecuteメソッド内で必要な初期化を完了するためのより良い方法はありますか、それともこの解決策はうまくいきますか?

注: とはスレッド セーフではないことを認識しており、上記の例のような製品コードでは使用しないでくださいAllocateHWndDeallocateHWnd

4

4 に答える 4

9

メインスレッド

  1. イベントを作成します。たとえば、TSimpleEventあなたのニーズには十分です。
  2. イベントを非シグナル状態に設定します。それTSimpleEventは への呼び出しResetEventです。新しく造られTSimpleEventたものは非信号状態になると思いますが、頭のてっぺんからその詳細を思い出せません。
  3. スレッドを作成し、コンストラクターでイベントを渡します。
  4. イベントがシグナル状態になるまで待ちます。それTSimpleEventは を呼び出すことを意味しWaitForます。

ワーカー スレッド

  1. スレッドのコンストラクターに渡されたイベントをメモします。
  2. スレッド実行の開始時に、イベントを通知します。それTSimpleEventは を呼び出すことを意味しSetEventます。
于 2015-02-19T14:14:32.160 に答える
8

FIsRunningからBooleanに変更してTEvent、すべての使用準備が整った場合にシグナルを受け取ります。

これで、いつでもこのイベントを待つことができます (特に のような公開メソッドでDoAction):

const
  WM_MY_ACTION = WM_APP + 10;

type
  TWndThread = class(TThread)
  private
    FIsRunning: TEvent; // <- event
  protected
    fWndHandle: THandle;
    procedure WndProc(var Msg: TMessage);
    procedure Execute; override;

    procedure CheckIsRunning; // guard method
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoAction;
  end;

constructor TWndThread.Create;
begin
  // create event
  FIsRunning := TEvent.Create( nil, True, False, '' );
  inherited;
end;

destructor Destroy;
begin
  inherited;
  // free event
  FIsRunning.Free;
end;

procedure CheckIsRunning;
begin
  // guard if terminated
  if Terminated then
    raise Exception.Create( 'Already terminated' );
  // wait for event
  FIsRunning.WaitFor();
end;

procedure TWndThread.Execute;
var
  Msg: TMsg;
begin
  fWndHandle := AllocateHWnd(WndProc);

  // set event
  FIsRunning.SetEvent;

  try
    while not Terminated do
      begin
        if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
          begin
            while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
              begin
                TranslateMessage(Msg);
                DispatchMessage(Msg);
              end;
          end;
      end;
  finally
    DeallocateHWnd(fWndHandle);
  end;
end;

procedure TWndThread.WndProc(var Msg: TMessage);
begin
  case Msg.Msg of
    WM_MY_ACTION:
      begin
        inc(Test);
      end;
    else Msg.Result := DefWindowProc(fWndHandle, Msg.Msg, Msg.WParam, Msg.LParam);
  end;
end;

procedure TWndThread.DoAction;
begin
  // guard method
  CheckIsRunning;
  // do the action
  PostMessage(fWndHandle, WM_MY_ACTION, 0, 0);
end;

今ではすべてが非常に使いやすく、待機する特別な理由がある場合 (DoActionメソッドへのアクセスが速すぎる)は、待機するだけで済みます。

var
  t: TWndThread;
begin
  t := TWndThread.Create;
  try
    t.DoAction;
  finally
    t.Free;
  end;
end;
于 2015-02-19T15:39:47.927 に答える
2

David が指摘したように、TEvent他の同期オブジェクトと同様に、これには a が機能します。例として(とにかくほとんど書き終わったので):

program Project1;

{$APPTYPE CONSOLE}

uses
  Classes, SysUtils, SyncObjs;

type
  TMyThread = class(TThread)
    private
      FWaitEvent : TEvent;
    public
      constructor Create(AWaitEvent : TEvent);
      procedure Execute; override;
      property WaitEvent : TEvent read FWaitEvent;
  end;

constructor TmyThread.Create(AWaitEvent: TEvent);
begin
  inherited Create(true);
  FWaitEvent := AWaitEvent;
end;

procedure TMyThread.Execute;
begin
  // maybe do something
  sleep(1000);
  FWaitEvent.SetEvent;
  // do more things
end;


var
  LMyThread : TMyThread;
  LWaitEvent : TEvent;
  LWaitResult : TWaitResult;
begin
  LWaitEvent := TEvent.Create;
  LMyThread := TMyThread.Create(LWaitEvent);
  try
    LMyThread.Start;
    WriteLn('Created Thread...');
    LWaitResult := LMyThread.WaitEvent.WaitFor(5000);

    case LWaitResult of
      wrSignaled : WriteLn('Waited successfully for thread start');
      wrTimeout : WriteLn('Timeout waiting for thread');
      wrAbandoned : WriteLn('Object freed already.');
      wrError : WriteLn('Wait error'); // check LastError
      wrIOCompletion :  // undocumented?
    end;
  finally
    LMyThread.WaitFor;
    LMyThread.Free;
    LWaitEvent.Free;
  end;
  ReadLn;
end.
于 2015-02-19T14:30:08.707 に答える