这个 Delphi 线程代码是否正确?
Is this Delphi Thread code correct?
我知道 Delphi 个话题已经在很多话题上讨论过了。我尝试查看它们,但没有找到问题的答案。
背景:
我发现在浏览器加载 Adobe Acrobat Reader DC 后释放 TWebBrowser 可能需要 10 秒以上。我认为它正在以某种方式检查更新或其他内容。试图关闭带有浏览器的表单时很烦人。
我想也许我可以让后台线程释放浏览器。所以我把浏览器变量移到了一个全局变量中(私密地存放在单元的实现部分)。一次只能使用其中一种形式。然后我试图让一个线程在后台释放它。它没有像我预期的那样工作。
示例代码
interface
TMyform = class(TForm)
pnlBowserHolder: TPanel;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
//WebBrowser : TWebBrowser; <-- moved to global variable
public
{ Public declarations }
end;
implementation
type
TBackgroundBrowserKillerThread = class(TThread)
public
procedure Execute; override;
end;
var
WebBrowser : TWebBrowser;
BrowserKillerThread : TBackgroundBrowserKillerThread;
procedure TfrmLabImageViewer.FormCreate(Sender: TObject);
begin
WebBrowser := TWebBrowser.Create(Self);
TWinControl(WebBrowser).Parent := pnlBowserHolder;
WebBrowser.Align := alClient;
end;
procedure TfrmLabImageViewer.FormDestroy(Sender: TObject);
begin
BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true);
Application.ProcessMessages;
BrowserKillerThread.Execute();
//WebBrowser.Free;
end;
procedure TBackgroundBrowserKillerThread.Execute();
begin
TWinControl(WebBrowser).Parent := nil;
FreeAndNil(WebBrowser);
self.FreeOnTerminate := true;
BrowserKillerThread := nil; //free reference to thread, shouldn't affect ability of self to free itself (?)
end;
问题:
- 当我在调试模式下单步执行 FormDestroy 代码时,包含 BrowserKillerThread.Execute(); 的行仍然需要 10 秒才能执行。我原以为这会立即启动另一个线程和 return。但事实并非如此。我对 .execute 的理解有误吗?或者发生了什么有趣的事情?
- 我做的是坏事吗?我读到 VCL 不是线程安全的,并且不能从另一个线程访问 VCL 对象t/shouldn。我希望这不会适用于这种情况,因为我只是释放对象而没有计划进一步的交互。
- 如果线程在终止时自行释放,我认为这会使我的 BrowserKillerThread 指针悬空。那么可以像我一样将 nil 分配给它吗?
- 有什么更好的建议吗?
非常感谢。
KT
Is my understanding wrong of what .execute does??
是的。这不是你做线程的方式。通常,您首先必须决定在最初启动线程后是否需要对该线程执行任何操作。如果是这样,请保留引用,不要使用 FreeOnTerminate
,并在线程终止后自行处理线程的销毁。没有的话就别留reference了,设置FreeOnTerminate
,发过去
您既不会在线程的执行中设置 FreeOnTerminate
,也不会保留对具有此设置的线程的全局引用。您也不会通过调用线程的 Execute
方法来启动线程,而是通过创建线程 non-suspended 或调用 Start
来启动线程。只是调用 Execute
并没有真正启动线程,而是在当前线程的上下文中执行这个过程,这就是为什么它仍然需要 10 秒。
您的示例将变为:
procedure TfrmLabImageViewer.FormDestroy(Sender: TObject);
var BrowserKillerThread: TBackgroundBrowserKillerThread;
begin
BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true);
BrowserKillerThread.FreeOnTerminate := true;
BrowserKillerThread.Start;
end;
procedure TBackgroundBrowserKillerThread.Execute();
begin
TWinControl(WebBrowser).Parent := nil; // I don't think you need to nil the parent here, but probably does no harm
FreeAndNil(WebBrowser);
end;
现在线程是对的,但是逻辑还是不对。正如您已经注意到的,您不能从主线程以外的线程修改 VCL 对象,这包括 Free
。您必须使用 Synchronize
执行此操作,这会破坏线程的目的。
我还认为在一个线程中终止浏览器的想法,否则它会花费太长时间,这只是在与症状作斗争,而不是与原因作斗争。我认为最好首先找出为什么需要这么长时间,然后解决这个问题,而不是试图以这种方式规避问题。但这超出了这个问题的范围,如果你有问题,你应该针对这个特定问题提出一个单独的问题。
我知道 Delphi 个话题已经在很多话题上讨论过了。我尝试查看它们,但没有找到问题的答案。
背景: 我发现在浏览器加载 Adobe Acrobat Reader DC 后释放 TWebBrowser 可能需要 10 秒以上。我认为它正在以某种方式检查更新或其他内容。试图关闭带有浏览器的表单时很烦人。
我想也许我可以让后台线程释放浏览器。所以我把浏览器变量移到了一个全局变量中(私密地存放在单元的实现部分)。一次只能使用其中一种形式。然后我试图让一个线程在后台释放它。它没有像我预期的那样工作。
示例代码
interface
TMyform = class(TForm)
pnlBowserHolder: TPanel;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
//WebBrowser : TWebBrowser; <-- moved to global variable
public
{ Public declarations }
end;
implementation
type
TBackgroundBrowserKillerThread = class(TThread)
public
procedure Execute; override;
end;
var
WebBrowser : TWebBrowser;
BrowserKillerThread : TBackgroundBrowserKillerThread;
procedure TfrmLabImageViewer.FormCreate(Sender: TObject);
begin
WebBrowser := TWebBrowser.Create(Self);
TWinControl(WebBrowser).Parent := pnlBowserHolder;
WebBrowser.Align := alClient;
end;
procedure TfrmLabImageViewer.FormDestroy(Sender: TObject);
begin
BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true);
Application.ProcessMessages;
BrowserKillerThread.Execute();
//WebBrowser.Free;
end;
procedure TBackgroundBrowserKillerThread.Execute();
begin
TWinControl(WebBrowser).Parent := nil;
FreeAndNil(WebBrowser);
self.FreeOnTerminate := true;
BrowserKillerThread := nil; //free reference to thread, shouldn't affect ability of self to free itself (?)
end;
问题:
- 当我在调试模式下单步执行 FormDestroy 代码时,包含 BrowserKillerThread.Execute(); 的行仍然需要 10 秒才能执行。我原以为这会立即启动另一个线程和 return。但事实并非如此。我对 .execute 的理解有误吗?或者发生了什么有趣的事情?
- 我做的是坏事吗?我读到 VCL 不是线程安全的,并且不能从另一个线程访问 VCL 对象t/shouldn。我希望这不会适用于这种情况,因为我只是释放对象而没有计划进一步的交互。
- 如果线程在终止时自行释放,我认为这会使我的 BrowserKillerThread 指针悬空。那么可以像我一样将 nil 分配给它吗?
- 有什么更好的建议吗?
非常感谢。
KT
Is my understanding wrong of what .execute does??
是的。这不是你做线程的方式。通常,您首先必须决定在最初启动线程后是否需要对该线程执行任何操作。如果是这样,请保留引用,不要使用 FreeOnTerminate
,并在线程终止后自行处理线程的销毁。没有的话就别留reference了,设置FreeOnTerminate
,发过去
您既不会在线程的执行中设置 FreeOnTerminate
,也不会保留对具有此设置的线程的全局引用。您也不会通过调用线程的 Execute
方法来启动线程,而是通过创建线程 non-suspended 或调用 Start
来启动线程。只是调用 Execute
并没有真正启动线程,而是在当前线程的上下文中执行这个过程,这就是为什么它仍然需要 10 秒。
您的示例将变为:
procedure TfrmLabImageViewer.FormDestroy(Sender: TObject);
var BrowserKillerThread: TBackgroundBrowserKillerThread;
begin
BrowserKillerThread := TBackgroundBrowserKillerThread.Create(true);
BrowserKillerThread.FreeOnTerminate := true;
BrowserKillerThread.Start;
end;
procedure TBackgroundBrowserKillerThread.Execute();
begin
TWinControl(WebBrowser).Parent := nil; // I don't think you need to nil the parent here, but probably does no harm
FreeAndNil(WebBrowser);
end;
现在线程是对的,但是逻辑还是不对。正如您已经注意到的,您不能从主线程以外的线程修改 VCL 对象,这包括 Free
。您必须使用 Synchronize
执行此操作,这会破坏线程的目的。
我还认为在一个线程中终止浏览器的想法,否则它会花费太长时间,这只是在与症状作斗争,而不是与原因作斗争。我认为最好首先找出为什么需要这么长时间,然后解决这个问题,而不是试图以这种方式规避问题。但这超出了这个问题的范围,如果你有问题,你应该针对这个特定问题提出一个单独的问题。