繁体   English   中英

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

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

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将无法成功将消息发布到包含的窗口句柄,因为它尚未创建。 基本上, WndProc inc(Test)不会被触发。

有没有更好的方法等待线程启动并在Execute方法中完成必要的初始化,或者这个解决方案是否得到了好处?

注意:我知道AllocateHWndDeallocateHWnd不是线程安全的,不应该像上面的例子那样在生产代码中使用。

主线程

  1. 创建一个事件。 例如, TSimpleEvent就足以满足您的需求。
  2. 将事件设置为无信号。 对于TSimpleEvent这是一个呼叫ResetEvent 我希望一个新铸造的TSimpleEvent会处于无信号状态,但是我无法记住这个细节。
  3. 创建线程,在构造函数中传递事件。
  4. 等待事件发出信号。 对于TSimpleEvent ,这意味着调用WaitFor

工人线程

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

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;

正如大卫所指出的那样, 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.

猜猜我有你的想法:

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;

或者你可以像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