从TThread.DoTerminate设置VCL控制属性

Sal*_*dor 2 delphi multithreading delphi-10-seattle

我正在使用该 TThread.DoTerminate方法通知主线程TThread已终止.但是,尽快尝试从DoTerminate内部更改某些控件(按钮)的属性,两个控件都会消失.

当我关闭表单时,我收到此消息

项目ProjectTest.exe引发了异常类EOSError,并显示消息"系统错误".代码:1400.无效的窗口句柄'.

这是一个重现问题的示例应用程序.

type
  TFooThread = class;

  TFormSample = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    ProgressBar1: TProgressBar;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FooThread : TFooThread;
    procedure ThreadIsDone;
  public
  end;

  TFooThread = class(TThread)
  private
    FForm : TFormSample;
  protected
    procedure DoTerminate; override;
  public
    procedure Execute; override;
    constructor Create(AForm : TFormSample); reintroduce;
    destructor Destroy; override;
  end;

var
  FormSample: TFormSample;

implementation

{$R *.dfm}

{ TFooThread }

constructor TFooThread.Create(AForm: TFormSample);
begin
  inherited Create(False);
  FreeOnTerminate := False;
  FForm := AForm;
end;

destructor TFooThread.Destroy;
begin
  inherited;
end;

procedure TFooThread.DoTerminate;
begin
  FForm.ThreadIsDone;
  inherited;
end;

procedure TFooThread.Execute;
var
  i : Integer;
begin
  for i := 1 to 100 do
  begin
    Synchronize(
     procedure
     begin
       FForm.ProgressBar1.Position := i;
     end
    );
    Sleep(50);
  end;

  Terminate();
end;

{ TFormSample }

procedure TFormSample.Button1Click(Sender: TObject);
begin
  FooThread := TFooThread.Create(Self);
  TButton(Sender).Enabled := false;
end;

procedure TFormSample.FormCreate(Sender: TObject);
begin
  FooThread := nil;
  Button3.Visible := False;
end;

procedure TFormSample.FormDestroy(Sender: TObject);
begin
  if (FooThread<>nil) then
  begin
    if not FooThread.Terminated then
     FooThread.WaitFor;
    FooThread.Free;
  end;
end;

procedure TFormSample.ThreadIsDone;
begin
  //this code is executed but the controls are not updated
  //both buttons just disappear from the form !!!!
  //Also if I remove these lines, no error is raised. 
  Button2.Visible := False;
  Button3.Visible := True;
end;

end.
Run Code Online (Sandbox Code Playgroud)

问题是:如何在TThread完成后立即更新某些VCL控件的属性?

Dis*_*ned 6

更新内部的控件DoTerminate(就像你一样)应该没问题.
DoTerminate在线程的上下文中运行.因此,从该方法更新控件是不安全的.基本实现同步对OnTerminate事件的调用.

所以OnTerminate已经同步了.从OnTerminate事件处理程序更新控件是安全的.

但是,我更倾向于在调用表单的线程类中没有代码,因为这会创建循环依赖项.而是让表单为OnTerminate事件分配处理程序.这样,控制表单的代码将在表单类中.您可以对控件更新执行相同操作以指示线程进度.

FooThread := TFooThread.Create(...);
             //WARNING: If you need to do **any**
             //initialisation after creating a
             //thread, it's better to create it
             //in a Suspended state.
FooThread.OnTerminate := ThreadIsDone;
//Of course you'll have to change the signature of ThreadIsDone accordingly.
FooThread.OnProgress := ThreadProgress;
//You'd have to define a suitable callback event on the thread.

//Finally, if the thread started in a suspended state, resume it.
FooThread.Start;
Run Code Online (Sandbox Code Playgroud)

避免循环依赖是一项更多工作,但大大简化了应用程序.

David提到您可以在运行状态下创建线程.要安全地这样做,你必须:

  • 将所有必要的初始化信息传递给构造函数.
  • 并且在构造函数内部调用继承的构造函数之前执行所有初始化.

你的Execute方法也有错误:

procedure TFooThread.Execute;
var
  i : Integer;
begin
  ...

  Terminate(); //This is pointless.
               //All it does is set Terminated := True;
end;
Run Code Online (Sandbox Code Playgroud)

线程退出时终止.对Terminate的所有调用都设置了一个内部标志,表示线程应该终止.您通常按如下方式编写Execute方法:

begin
  while not Terminated do
  begin
    ...
  end;
end;
Run Code Online (Sandbox Code Playgroud)

然后你的表单可能有一个调用的按钮: FooThread.Terminate();

这将导致while循环在当前迭代结束时退出.这允许线程"优雅地"退出.

  • 这个答案有一些问题.`DoTerminate`在线程上执行.可以在那里做VCL的东西.不推荐使用`Resume`,使用`Start`.如果在构造函数中初始化,则无需暂停. (2认同)