Atak_Snajpera
Atak_Snajpera

Reputation: 629

How to automatically execute FreeAndNill() after thread termination

At the moment I'm using additional thread to nicely free memory after thread. Before you ask. No I can't use FreeOnTerminate:=true because I need .waitfor. I also need FreeAndNil() because only in this way I can check if thread is running using Assigned(). Example code.

procedure TForm1.Button1Click(Sender: TObject);
begin  

  SupervisorThread:= TSupervisorThread.Create(True);
  SupervisorThread.FreeOnTerminate:=false; //MUST BE FALSE!
  SupervisorThread.Priority := tpNormal;
  SupervisorThread.Resume;

end;

procedure TSupervisorThread.Execute;
begin

  CleaningThread:= TCleaningThread.Create(True);
  CleaningThread.FreeOnTerminate:=true;
  CleaningThread.Priority := tpNormal;
  CleaningThread.Resume;

  //some loops here

end;

procedure TCleaningThread.Execute;
begin

  if Assigned(SupervisorThread)=true then
  begin
    SupervisorThread.WaitFor;
    FreeAndNil(SupervisorThread);
  end;

end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin

  if Assigned(SupervisorThread)=false then CanClose:=true  
  else 
  begin
    CanClose:=false;
    ShowMessage('Cannot close form because SiupervisorThread is still working'); 
  end;

end;

Upvotes: 4

Views: 1085

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595742

You can use the TThread.OnTerminate event to detect when a thread has finished running, eg:

procedure TForm1.Button1Click(Sender: TObject);
begin  
  if not Assigned(SupervisorThread) then
  begin
    SupervisorThread:= TSupervisorThread.Create(True);
    SupervisorThread.FreeOnTerminate := False;
    SupervisorThread.Priority := tpNormal;
    SupervisorThread.OnTerminate := SupervisorThreadTerminated;
    SupervisorThread.Resume;
  end;
end;

procedure TForm1.SupervisorThreadTerminated(Sender: TObject);
begin
  SupervisorThread := nil;
end;

However, this creates some problems. It creates a race condition, since the cleaning thread acts on the SupervisorThread pointer, which could disappear at any time while the cleaning thread is still running. And it creates a memory leak, as you still need to free the SupervisorThread object after it has terminated, but you can't do that in the OnTerminate handler directly.

A better solution would not rely on the SupervisorThread pointer at all.

var
  SupervisorTerminated: TEvent;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SupervisorTerminated := TEvent.Create(nil, True, True, '');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(SupervisorThread) then
  begin
    SupervisorThread.Terminate;
    while SupervisorTerminated.WaitFor(1000) = wrTimeout do
      CheckSynchronize;
  end;
  SupervisorTerminated.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin  
  if not Assigned(SupervisorThread) then
  begin
    SupervisorThread := TSupervisorThread.Create(True);
    SupervisorThread.FreeOnTerminate := True;
    SupervisorThread.Priority := tpNormal;
    SupervisorThread.OnTerminate := SupervisorThreadTerminated;
    SupervisorTerminated.ResetEvent;
    SupervisorThread.Resume;
  end;
end;

procedure TForm1.SupervisorThreadTerminated(Sender: TObject);
begin
  SupervisorThread := nil;
  SupervisorTerminated.SetEvent;
end;

procedure TCleaningThread.Execute;
begin
  SupervisorTerminated.WaitFor(INFINITE);
end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := (SupervisorTerminated.WaitFor(0) = wrSignaled);
  if not CanClose then
    ShowMessage('Cannot close form because Supervisor Thread is still working'); 
end;

Upvotes: 1

user7590204
user7590204

Reputation:

Use the TThread.OnTerminate event:

private
  procedure DoTerminateEvent(Sender: TObject);

var
  isRunning: Boolean;

procedure TForm2.DoTerminateEvent(Sender: TObject);
begin
  isRunning := False;
end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if (isRunning) then
  begin
    CanClose := false;
    ShowMessage('Cannot close form because SupervisorThread is still working')
  end else
    CanClose := true;
end;

Set the OnTerminate handler when creating the Thread:

SupervisorThread := TSupervisorThread.Create(True);
...
SupervisorThread.OnTerminate := DoTerminateEvent;
SupervisorThread.Resume;

Or, pass it as a parameter to the Thread's constructor:

TSupervisorThread = class(TThread)
public
  constructor Create(OnTerminatEvent: TNotifyEvent);
end;

procedure TThreadCustom.Create(OnTerminateEvent: TNotifyEvent);
begin
  inherited Create(True);
  OnTerminate := OnTerminateEvent;
end;

SupervisorThread := TSupervisorThread.Create(DoTerminateEvent);

Upvotes: 4

Related Questions