I know that Delphi threads have been discussed on many threads. I tried reviewing them, but didn't find an answer to my question.
Background: I have found that freeing a TWebBrowser can take 10+ second after the browser loaded up Adobe Acrobat Reader DC. I think somehow it is checking for updates or something. It is annoying when trying to close a form with a browser in it.
I thought perhaps I could have a background thread release the browser. So I moved the browser variable to a global variable (privately stored in the implementation part of the unit). Only one of these forms would be used at a time. Then I tried to have a thread free it in the background. It's not working as I would have expected.
Example code
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;
Questions:
Thanks so much in advance.
KT
Is my understanding wrong of what .execute does??
Yes. This is not how you do threading. Generally, you first have to decide whether you need to do anything whatsoever with the thread after you have initially started it. If so, keep a reference, don't use FreeOnTerminate
, and take care of the thread's destruction yourself after it has terminated. If you don't, don't keep a reference, set FreeOnTerminate
, and send it off.
You would neither set FreeOnTerminate
within a thread's execute, nor keep a global reference to a thread that has this set. You also don't start a thread by calling it's Execute
method, but by either creating it non-suspended, or by calling Start
. Just calling Execute
doesn't actually start the thread, but executes this procedure within the context of the current thread, that's why it still takes 10 seconds.
Your example would become:
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;
Now the threading is correct, but the logic is still wrong. As you already noticed you must not modify VCL objects from threads other than the main thread, this includes Free
. You'd have to do this with Synchronize
, which would defeat the purpose of threading.
I also think that the idea of killing the browser in a thread because otherwise it takes too long is only battling the symptoms, rather than the cause. I think it would be best to find out why that takes so long in the first place, and fix that, rather than trying to circumvent the issue this way. But this is outside of the scope of this question, if you have issues with that you should ask a separate question for this specific issue.