等待Thread实例启动的正确方法是什么

时间:2015-02-19 14:11:15

标签: delphi tthread

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 个答案:

答案 0 :(得分:9)

主要帖子

  1. 创建活动。例如,TSimpleEvent就足以满足您的需求。
  2. 将事件设置为无信号。致TSimpleEvent的{​​{1}}。ResetEvent。我希望新铸造的TSimpleEvent处于无信号状态,但是我不记得那个细节。
  3. 创建线程,在构造函数中传递事件。
  4. 等待事件发出信号。 TSimpleEvent表示调用WaitFor
  5. 工作线程

    1. 记下传递给线程构造函数的事件。
    2. 在线程执行开始时,发出事件信号。 TSimpleEvent表示调用SetEvent

答案 1 :(得分:8)

FIsRunningBoolean更改为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;

答案 2 :(得分:2)

正如大卫所指出的那样,TEvent可以为此工作,就像任何数量的其他同步对象一样。作为例子(因为我大部分时间都写完了):

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.

答案 3 :(得分:0)

猜猜我明白了:

uses
  Windows, SysUtils, Classes;

type
  TMyThread = class(TThread)
  private
    FStartEvent: THandle;
  protected
    procedure Execute; override;
  public
    procedure AfterConstruction; override;
  end;

implementation

{ TMyThread }

procedure TMyThread.AfterConstruction;
begin
  FStartEvent:= CreateEvent(nil, True, False, nil);
  inherited;  // this starts the thread
  if WaitForSingleObject(FStartEvent, INFINITE) = WAIT_FAILED
 // means the thread finished;
 // should not happen but you can check it to be safe
    then ..;
 // otherwise nothing should be done
end;

procedure TMyThread.Execute;
begin
  SetEvent(FStartEvent);
// ...
  CloseHandle(FStartEvent);
end;

或者你可以像Rufo先生提议的那样将WaitForSingleObjectAfterConstruction移到DoAction代码:

procedure TMyThread.CheckRunning;
begin
  if WaitForSingleObject(FStartEvent, INFINITE) = WAIT_FAILED
  then // the thread already finished;
       // this should not normally happen,
       // maybe application is terminating or something else unexpected.
//   ..;
// else the thread is up and running here. 
end;