'How to wait that all anonymous thread are terminated before closing the app?
I encounter an awkward problem. In my app I often do
TThread.createAnonymousThread(
procedure
....
end).start
The problem I have is that when I close the main form of my app, then sometime some of those AnonymousThread are still alive after the Tform.destroy finished . Is their a way in my Tform.destroy to wait that all those AnonymousThread (created a little everywhere in the whole app) are successfully terminated before to continue ?
I found this way to list all running thread (from How can I get a list with all the threads created by my application) :
program ListthreadsofProcess;
{$APPTYPE CONSOLE}
uses
PsAPI,
TlHelp32,
Windows,
SysUtils;
function GetTthreadsList(PID:Cardinal): Boolean;
var
SnapProcHandle: THandle;
NextProc : Boolean;
TThreadEntry : TThreadEntry32;
begin
SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); //Takes a snapshot of the all threads
Result := (SnapProcHandle <> INVALID_HANDLE_VALUE);
if Result then
try
TThreadEntry.dwSize := SizeOf(TThreadEntry);
NextProc := Thread32First(SnapProcHandle, TThreadEntry);//get the first Thread
while NextProc do
begin
if TThreadEntry.th32OwnerProcessID = PID then //Check the owner Pid against the PID requested
begin
Writeln('Thread ID '+inttohex(TThreadEntry.th32ThreadID,8));
Writeln('base priority '+inttostr(TThreadEntry.tpBasePri));
Writeln('');
end;
NextProc := Thread32Next(SnapProcHandle, TThreadEntry);//get the Next Thread
end;
finally
CloseHandle(SnapProcHandle);//Close the Handle
end;
end;
begin
{ TODO -oUser -cConsole Main : Insert code here }
GettthreadsList(GetCurrentProcessId); //get the PID of the current application
//GettthreadsList(5928);
Readln;
end.
but it's look like that in this list their is some threads that are not really made by my code and that those threads never close. For example for a blank project this is the list of threads :
Solution 1:[1]
Core problem you are facing does not come from the anonymous threads as such, but from self-destroying anonymous threads - the ones that have FreeOnTerminate
set.
In order to wait on a thread, you need to have reference to a thread or its handle (Windows platform). Because you are dealing with self-destroying threads, taking reference to a thread is not an option, because as soon as you start the thread you are no longer allowed to touch that reference.
Delphi RTL does not perform any cleanup for the self destroying anonymous threads during application shutdown, so those threads will be just killed by the OS after your application main form is destroyed, hence your problem.
One of the solutions that will allow you to wait for anonymous threads, that does not require any complicated housekeeping and maintaining any kind of lists, and that also requires minimal changes to the code that can be done with simple find and replace, is using TCountdownEvent
to count threads.
This requires replacing TThread.CreateAnonymousThread
with constructing custom thread class TAnoThread.Create
(you can add static factory method if you like, instead of directly calling constructor) that will have same behavior as anonymous thread, except its instances will be counted and you will be able to wait on all such threads to finish running.
type
TAnoThread = class(TThread)
protected
class var
fCountdown: TCountdownEvent;
class constructor ClassCreate;
class destructor ClassDestroy;
public
class procedure Shutdown; static;
class function WaitForAll(Timeout: Cardinal = INFINITE): TWaitResult; static;
protected
fProc: TProc;
procedure Execute; override;
public
constructor Create(const aProc: TProc);
end;
class constructor TAnoThread.ClassCreate;
begin
fCountdown := TCountdownEvent.Create(1);
end;
class destructor TAnoThread.ClassDestroy;
begin
fCountdown.Free;
end;
class procedure TAnoThread.Shutdown;
begin
fCountdown.Signal;
end;
class function TAnoThread.WaitForAll(Timeout: Cardinal): TWaitResult;
begin
Result := fCountdown.WaitFor(Timeout);
end;
constructor TAnoThread.Create(const aProc: TProc);
begin
inherited Create(True);
fProc := aProc;
FreeOnTerminate := True;
end;
procedure TAnoThread.Execute;
begin
if fCountdown.TryAddCount then
try
fProc;
finally
fCountdown.Signal;
end;
end;
And then you can add following code in your form destructor or any other appropriate place and wait for all anonymous threads to finish running.
destructor TForm1.Destroy;
begin
TAnoThread.Shutdown;
while TAnoThread.WaitForAll(100) <> wrSignaled do
CheckSynchronize;
inherited;
end;
Principle is following: countdown event is created with value 1, and when that value reaches 0, event will be signaled. To initiate shutdown, you call Shutdown
method which will decrease initial count. You cannot call this method more than once because it would mess up the count.
When anonymous thread Execute
method starts, it will first attempt to increase the count. If it cannot do that, it means countdown event is already signaled and thread will just terminate without calling its anonymous method, otherwise anonymous method will run and after it finishes count will be decreased.
If anonymous threads use TThread.Synchronize
calls, you cannot just call WaitForAll
because calling it from the main thread will deadlock. In order to prevent deadlock while you are waiting for the threads to finish, you need to call CheckSynchronize
to process pending synchronization requests.
This solution counts all threads of the TAnoThread
class regardless of whether they are self-destroying or not. This can be easily changed to count only those that have FreeOnTerminate
set.
Also, when you call Shutdown
, and you still have some running threads, new threads will still be able to start at that point because countdown even is not signaled. If you want to prevent new threads from that point on, you will need to add a Boolean flag that will indicate you have initiated shutdown process.
Solution 2:[2]
Reading all threads from the process and then trying to figure out which ones to wait for sounds like the path to a lot of pain.
If you really don't want to store the references of the anonymous threads (which then by the way should not be FreeOnTerminate as that would cause dangling references if the thread ends before you wait for it) then build a wrapper around TThread.CreateAnonymousThread
or TTask.Run
which does the storage internally and encapsulates the WaitFor
. You could even be fancy and add an additional group key so you can create and wait for different set of threads/tasks instead of all.
Solution 3:[3]
Push in a thread-safe list the TThread
references returned by CreateAnonymousThread
, make sure to keep it in sync when a thread terminates and implement your own WaitForAll
.
Or, consider to use TTask
class for a simple parallel threads management. It have already implemented WaitForAll
method.
Sample code took from Delphi help:
procedure TForm1.MyButtonClick(Sender: TObject);
var
tasks: array of ITask;
value: Integer;
begin
Setlength (tasks ,2);
value := 0;
tasks[0] := TTask.Create (procedure ()
begin
sleep(3000);
TInterlocked.Add (value, 3000);
end);
tasks[0].Start;
tasks[1] := TTask.Create (procedure ()
begin
sleep (5000);
TInterlocked.Add (value, 5000);
end);
tasks[1].Start;
TTask.WaitForAll(tasks);
ShowMessage ('All done: ' + value.ToString);
end;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Dalija Prasnikar |
Solution 2 | Stefan Glienke |
Solution 3 | Marcodor |