首页 > 解决方案 > 使用此表单的句柄在表单/框架的 OnCreate 中启动线程

问题描述

我有一个问题,我不知道如何解决。

我尝试在OnCreate事件中启动一个线程,或者在创建一个TFramewhen it Parentis still之后启动一个线程nil。创建线程时,我向它传递了一个窗口句柄,但是窗口的地址在OnShow事件之后发生了变化。

procedure Form1.OnCreate(Sender: TObject);
begin
  TCustomThread.Create(Self);
  Label1.Caption := IntToStr(Self.Handle); //for example 10203040 
end;

procedure Form1.ButtonOnClick;
begin
  Label1.Caption := IntToStr(Self.Handle); //i give 342545454 not 10203040 
end;

procedure Form1.FromThread(var Msg: TMessage); message WM_TheardComplete;
begin
  {do something}
end;

constructor TCustomThread.Create(AWinControl: TWinControl);
begin
  inherited Create(False);
  FWinControl := AWinControl;
  FreeOnTerminate := True;
end;

procedure TCustomThread.Execute;
begin
   {do something}
   PostMessage(FWinControl.Handle, WM_TheardComplete, 0, 0); //Handle 10203040
end;

我可以使用什么参数来启动线程,以便以后可以向该对象发送消息?

标签: delphi

解决方案


TWinControl.Handle属性不是线程安全的。VCL 可以并且确实在控件的生命周期内动态地重新创建控件的窗口,甚至多次。但更重要的是,窗口具有线程亲和性,给定窗口的消息检索和处理仅在创建窗口的线程中工作。使用控件Handle属性的工作线程会导致竞争条件,如果您不小心,实际上可能会导致工作线程捕获控件窗口的所有权,从而使控件在主 UI 线程中完全无用。

如果您需要为工作线程提供一个窗口来发布/发送消息,请为线程提供一个VCL 不会破坏(无需您告诉它)的持久窗口,例如通过使用TApplication窗口,使用其OnMessage事件处理已发布的消息,或其HookMainWindow()处理已发送消息的方法,例如:

procedure Form1.OnCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
  TCustomThread.Create(Application.Handle);
end;

procedure Form1.OnDestroy(Sender: TObject);
begin
  Application.OnMessage := nil;
end;

procedure Form1.AppMessage(var Msg: tagMSG; var Handled: Boolean);
begin
  if Msg.message = WM_TheardComplete then
  begin
    Handled := True;
    {do something}
  end;
end;

constructor TCustomThread.Create(AWnd: HWND);
begin
  inherited Create(False);
  FWnd := AWnd;
  FreeOnTerminate := True;
end;

procedure TCustomThread.Execute;
begin
   {do something}
   PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;

或者更好的是,使用使用 VCL 功能创建的新专用窗口 AllocateHWnd(),例如:

procedure Form1.OnCreate(Sender: TObject);
begin
  ThreadWnd := AllocateHWnd(ThreadWndProc);
  TCustomThread.Create(ThreadWnd);
end;

procedure Form1.OnDestroy(Sender: TObject);
begin
  DeallocateHWnd(ThreadWnd);
end;

procedure Form1.ThreadWndProc(var Message: TMessage);
begin
  if Message.Msg = WM_TheardComplete then
  begin
    {do something}
  end else
    Message.Result := DefWindowProc(ThreadWnd, Message.Msg, Message.WParam, Message.LParam);
end;

constructor TCustomThread.Create(AWnd: HWND);
begin
  inherited Create(False);
  FWnd := AWnd;
  FreeOnTerminate := True;
end;

procedure TCustomThread.Execute;
begin
   {do something}
   PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;

但是,在您提供的示例中,我建议使用完全不同的方法,而不是在线程执行结束时发送消息 - 使用TThread.OnTerminate已经与主线程同步的事件,例如:

procedure Form1.OnCreate(Sender: TObject);
var
  Thread: TCustomThread;
begin
  Thread := TCustomThread.Create;
  Thread.OnTerminate := ThreadFinished;
  Thread.Start; // or Resume() in older versions
end;

procedure Form1.ThreadFinished(Sender: TObject);
begin
  {do something}
end;

constructor TCustomThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;

procedure TCustomThread.Execute;
begin
   {do something}
end;

或者,在现代版本的 Delphi 中,考虑TThread.CreateAnonymousThread()改用,例如:

procedure Form1.OnCreate(Sender: TObject);
var
  Thread: TThread;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      {do something}
    end
  );
  Thread.OnTerminate := ThreadFinished;
  Thread.Start;
end;

procedure Form1.ThreadFinished(Sender: TObject);
begin
  {do something}
end;

甚至:

procedure Form1.OnCreate(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      try
        {do something}
      finally
        TThread.Queue(nil,
          procedure
          begin
            {do something}
          end
       );
      end;
    end
  ).Start;
end;

推荐阅读