I have a task I want to run in the background and not interrupt the GUI thread to check for new program versions.
When the application starts, it immediately queues a thread to wait 15 seconds then execute the remainder of the code. If the check is manually triggered before the 15 seconds are up, the existing automatic thread should be terminated. If the application is closed at any point, any remaining threads should be terminated as soon as possible.
I use 2 minutes in the example below for easier debugging.
My issue right now is that no combination that I've tried using WaitFor, Terminate, FreeOnTerminate, and OnTerminated will get the desired result. Either Destroy isn't called and I get memory leaks, the application hangs when terminating a thread, or I get Cannot terminate externally created thread exceptions.
Thread code
unit unCheckThread;
interface
uses SysUtils, Classes, SyncObjs, Dialogs;
type
TCheckThread = class(TThread)
private
FDelayEvent: TEvent;
FDelay: Integer;
public
constructor Create(const ADelay: Integer);
destructor Destroy; override;
procedure Execute; override;
procedure TerminatedSet; override;
end;
implementation
{ TCheckThread }
constructor TCheckThread.Create(const ADelay: Integer);
begin
inherited Create(True);
FreeOnTerminate := False;
FDelay := ADelay;
FDelayEvent := TEvent.Create(nil, True, False, '');
end;
destructor TCheckThread.Destroy;
begin
FDelayEvent.Free;
inherited;
end;
procedure TCheckThread.Execute;
begin
FDelayEvent.WaitFor(MSecsPerSec * FDelay);
{ if another thread has checked while waiting for the delay, cancel this check }
if Terminated then Exit;
{ some long running code }
Sleep(10000);
if Terminated then Exit;
Synchronize(
procedure()
begin
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
end);
end;
procedure TCheckThread.TerminatedSet;
begin
FDelayEvent.SetEvent;
end;
end.
UI
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, unCheckThread, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure onterminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ?
end;
AThread := TCheckThread.Create(0); // start immediately
AThread.OnTerminate := onterminate;
AThread.Start;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ??
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AThread := TCheckThread.Create(SecsPerMin * 2); // wait for 2 mintues before starting
end;
procedure TForm1.onterminate(Sender: TObject);
begin
FreeAndNil(AThread);
end;
end.