从 TThread.DoTerminate 设置 VLC 控件属性
Settting VCL controls properties from TThread.DoTerminate
我正在使用 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
事件处理程序更新控件是安全的。
但是,我更倾向于 而不是 在调用表单的线程 class 中有代码,因为这会创建循环依赖。而是让表单为 OnTerminate
事件分配一个处理程序。这样,控制表单的代码将采用 class 形式。您可以对控件更新执行相同操作以指示线程进度。
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 循环在当前迭代结束时退出。这允许线程“优雅地”退出。
我正在使用 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
事件处理程序更新控件是安全的。
但是,我更倾向于 而不是 在调用表单的线程 class 中有代码,因为这会创建循环依赖。而是让表单为 OnTerminate
事件分配一个处理程序。这样,控制表单的代码将采用 class 形式。您可以对控件更新执行相同操作以指示线程进度。
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 循环在当前迭代结束时退出。这允许线程“优雅地”退出。