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 所指出的, aTEvent可以为此工作,任何数量的其他同步对象也可以。举个例子(因为我基本上已经写完了):

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 回答
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;
于 2015-02-19T16:48:36.953 回答