简体   繁体   English

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

[英]What is the proper way to wait for TThread instance to start up

Between time of TThread instance creation and start up, main thread will continue with code execution. TThread实例创建和启动之间,主线程将继续执行代码。 If code in main thread depends on thread in question to be fully up and running it has to wait somehow until thread Execute method actually started. 如果主线程中的代码依赖于有问题的线程完全启动并运行,它必须以某种方式等待,直到线程Execute方法实际启动。

Consider following code: 考虑以下代码:

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;

Without loop that waits for IsRunning flag, DoAction will not be able to successfully post message to contained window handle because it will not yet be created. 如果没有等待IsRunning标志的循环, DoAction将无法成功将消息发布到包含的窗口句柄,因为它尚未创建。 Basically, inc(Test) inside WndProc will not be triggered. 基本上, WndProc inc(Test)不会被触发。

Is there a better way to wait for thread start up and complete necessary initialization inside Execute method or is this solution as good as it gets? 有没有更好的方法等待线程启动并在Execute方法中完成必要的初始化,或者这个解决方案是否得到了好处?

Note: I am aware that AllocateHWnd and DeallocateHWnd are not thread safe and should not be used in production code like above example. 注意:我知道AllocateHWndDeallocateHWnd不是线程安全的,不应该像上面的例子那样在生产代码中使用。

Main thread 主线程

  1. Create an event. 创建一个事件。 For instance, TSimpleEvent will suffice for your needs. 例如, TSimpleEvent就足以满足您的需求。
  2. Set the event to be non-signaled. 将事件设置为无信号。 For TSimpleEvent that's a call to ResetEvent . 对于TSimpleEvent这是一个呼叫ResetEvent I expect that a newly minted TSimpleEvent would be in the non-signaled state, but off the top of my head I cannot remember that detail. 我希望一个新铸造的TSimpleEvent会处于无信号状态,但是我无法记住这个细节。
  3. Create the thread, passing the event in the constructor. 创建线程,在构造函数中传递事件。
  4. Wait for the event to become signaled. 等待事件发出信号。 For TSimpleEvent that means calling WaitFor . 对于TSimpleEvent ,这意味着调用WaitFor

Worker thread 工人线程

  1. Make a note of the event passed to the thread's constructor. 记下传递给线程构造函数的事件。
  2. At the start of the thread execution, signal the event. 在线程执行开始时,发出事件信号。 For TSimpleEvent that means calling SetEvent . 对于TSimpleEvent ,这意味着调用SetEvent

Change FIsRunning from Boolean to TEvent to get signaled if everything is ready to use. FIsRunningBoolean更改为TEvent ,以便在所有内容都可以使用时发出信号。

Now you can wait for this event at any point (especially in the public methods like DoAction ): 现在您可以随时等待此事件(特别是在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;

Now everything is very easy to use and you only have to wait, if there is a special reason for waiting (accessing the DoAction method too fast) 现在一切都很容易使用,你只需等待,如果有特殊原因等待(访问DoAction方法太快)

var
  t: TWndThread;
begin
  t := TWndThread.Create;
  try
    t.DoAction;
  finally
    t.Free;
  end;
end;

As David noted, a TEvent would work for this, as would any number of other synchronization objects. 正如大卫所指出的那样, TEvent会为此工作,就像任何其他同步对象一样。 As example (since I was mostly done writing it anyway) : 作为例子(因为我大部分时间都写完了):

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.

Guess I got your idea: 猜猜我有你的想法:

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;

or else you can move WaitForSingleObject from AfterConstruction to your DoAction code as Sir Rufo proposed: 或者你可以像DoAction 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM