multithreading - 从 Thread.Terminate 设置 VCL 控件属性

标签 multithreading delphi delphi-10-seattle

我正在使用 TThread.DoTerminate 方法通知主线程 TThread 已终止。但是一旦尝试从 DoTerminate 内部更改某些控件(按钮)的属性,这两个控件就会从表单中消失。

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

Project ProjectTest.exe raised exception class EOSError with message 'System Error. Code: 1400. Invalid window handle'.

这是重现问题的示例应用程序。

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.

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

最佳答案

更新 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;

避免循环依赖需要多做一些工作,但会大大简化应用程序。

David mentions that you can create your thread in a running state. To do so safely you must:

  • Pass all necessary initialisation information into the constructor.
  • And inside the constructor perform all initialisation before calling the inherited constructor.

此外,您的 Execute 方法也有错误:

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

  Terminate(); //This is pointless.
               //All it does is set Terminated := True;
end;

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

begin
  while not Terminated do
  begin
    ...
  end;
end;

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

这将导致您的 while 循环在当前迭代结束时退出。这允许线程“优雅地”退出。

关于multithreading - 从 Thread.Terminate 设置 VCL 控件属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36299050/

相关文章:

delphi - 将 JVCL 安装到 Delphi 10 西雅图

c++ - 多线程应用程序中的关键部分和 if,else 条件

android - 何时关闭 Sqlite 数据库

使用 Borland Delphi 远程查询 SQL Server 2005

Delphi - 如何重新启用调试器异常通知?

delphi - 使用 Delphi 10 编译 SelectDirectory 第三次重载

delphi - 使用控制台应用程序报告关闭时的内存泄漏

c++ - 启用多线程 Eclipse C++

java - ClassCastException NativeRegExpExecResult 无法转换为 NativeArray

delphi - 在 Delphi TListView 中移动 SubItemImages 的错误水平位置?