Log in

View Full Version : [Delphi] Fast and easy way to terminate thread?


Atak_Snajpera
3rd July 2011, 14:00
At the beginning I was using brutal TerminateThread() function. It is probably the easiest way to terminate thread
nevertheless everybody on internet strongly recommends to not using it. So now I'm trying to figure out how to escape from thread code in the fastest and easiest way without many 'IF' in code.

Sample pseudocode


SomeThread
var x:integer;
begin
Repeat
if form1.jvthread1.terminated=false then DoSomeTask1();
if form1.jvthread1.terminated=false then DoSomeTask2();
if form1.jvthread1.terminated=false then DoSomeTask3();
if form1.jvthread1.terminated=false then DoSomeTask4();
x:=x+1;
until (form1.jvthread1.terminated=true) or (x>100);
end;


Using Ifs in almost every line could be a very painful process with complex code.

Any better ideas?

LoRd_MuldeR
3rd July 2011, 14:13
What exactly are you trying do to? Why has the thread to be terminated before it's done with its work?

Anyway, it is correct that you should never kill or suspend a thread "from outside" (e.g. from you main thread). Instead wait for the thread to terminate normally. And, if you have to, signal to the thread that it should terminate as soon as possible, for example by setting a 'terminate' flag. This of course requires your code inside the thread to check for the terminate flag at regular intervals.

The "worker" thread code might look like:
procedure TMyThread.Execute;
var
begin
Initialize(); //Initialization, allocate resources and stuff here

while MoreWorkLeft() and (not Terminated) do
begin
DoSomeWork(); //This function should do the next chunk of work, but shouldn't take too much time to return
end;

Cleanup(); //Do the final clean up here (e.g. free up resources) - executed even if thread was terminated!
end;

The main thread would cause the "worker" threads to terminate like this:

procedure StopThreads;
begin
for i := 0 to NumThreads-1 do
begin
threads[i].Terminate(); //This does not kill the thread, it just sets the thread's Terminated flag to TRUE
threads[i].WaitFor(); //Wait for the thread to actually terminate...
end;
end;

Note that WaitFor() can lead to a Deadlock, if the Terminated flag is not checked/handled properly inside the thread!

Atak_Snajpera
3rd July 2011, 14:32
The "worker" thread code might look like:
Well this looks exactly what I do now with "IF"

The main thread would cause the "worker" threads to terminate like this:
I use same method.

I guess I won't figure out more than this.

LoRd_MuldeR
3rd July 2011, 14:34
Well this looks exactly what I do now with "IF"

I use same method.

I guess I won't figure out more than this.

Well, that's how these kind of things are commonly done.

If you want the thread to be able to exit gracefully and timely, it has to check for the 'terminated' flag regularly.

Your DoSomeTaskX() methods should check the flag too, if they contain a length loop or alike...

Atak_Snajpera
3rd July 2011, 14:41
At least now I'm sure that there is no magic TerminateThread ;)

I'm lazy so I use ready to use JV components (JVThread). Instead of .waifor() I use this

while form1.JvThread1.OneThreadIsRunning=true do Application.ProcessMessages;

or

repeat Application.ProcessMessages until form1.JvThread1.OneThreadIsRunning=false

Is this the same as .waitfor()?

LoRd_MuldeR
3rd July 2011, 16:46
Nope. It isn't.

WaitFor() doesn't return until the thread has terminated. Calling this from the "main" thread of your application will make the GUI freeze, as no messages are processed while waiting.

Calling Application.ProcessMessages() in a loop until the thread has terminated will keep the application responsive. However this kind of "busy waiting" mechanism might be a waste CPU cycles.

It's probably more convenient (and more CPU friendly) to just use the threads OnTerminate() event handler instead of waiting in a loop...

procedure TForm1.FormCreate(Sender: TObject);
begin
MyThread := nil;
end;

procedure TForm1.ButtonStartClick(Sender: TObject);
begin
ButtonStart.Enabled := false;
if(MyThread <> nil) then MyThread.Free();
MyThread := TMyThread.Create(true);
MyThread.OnTerminate := self.ThreadFinished;
MyThread.Resume;
end;

procedure TForm1.ThreadFinished(Sender: TObject);
begin
ShowMessage('Thread has finished!');
ButtonStart.Enabled := true;
end;


If you do the busy waiting, at least add a sleep instruction:
while form1.JvThread1.OneThreadIsRunning do
begin
Application.ProcessMessages;
Sleep(250);
end;

Also note that when checking a TThread's 'Terminated' property from "outside" (e.g. from your main thread), it only checks whether the terminate flag has been set for thread, it does NOT check whether the thread has actually finished yet! It seems on newer Delphi versions there now is a 'Finished' property, but Delphi 7.0 did not expose this. Maybe Jv(Base)Thread does though...

BTW: There is no need to compare against true/false. For functions that return a Boolean, you can do "while Foo" or "while not Foo".

Atak_Snajpera
4th July 2011, 12:22
Also note that when checking a TThread's 'Terminated' property from "outside" (e.g. from your main thread), it only checks whether the terminate flag has been set for thread, it does NOT check whether the thread has actually finished yet! It seems on newer Delphi versions there now is a 'Finished' property, but Delphi 7.0 did not expose this. Maybe Jv(Base)Thread does though...
JVThread has this already builtin
form1.JvThread1.OneThreadIsRunning=true/false
so this is not problem for me.

However when I was still using "hand made" threads i just used global variables to check if thread is activated or terminated


ThreadCode
Begin
Thread1_Activated:=true;

DoSomeWork();


Thread1_Activated:=false;
End;

LoRd_MuldeR
4th July 2011, 13:37
Just a side note: If global variables are accessed by several threads, they should be guarded by using a critical section!

type
TMyThread = class(TThread)
private
ActiveFlag: Boolean;
Mutex: TRTLCriticalSection;
function GetActiveFlag: Boolean;
procedure SetActiveFlag(NewValue: Boolean);
public
constructor Create(Suspended: Boolean);
destructor Destroy; override;
property IsActive: Boolean read GetActiveFlag;
protected
procedure Execute; override;
procedure DoWork;
end;

implementation

{ TMyThread }

constructor TMyThread.Create(Suspended: Boolean);
begin
inherited;
InitializeCriticalSection(Mutex);
ActiveFlag := false;
end;

destructor TMyThread.Destroy;
begin
inherited;
DeleteCriticalSection(Mutex);
end;

function TMyThread.GetActiveFlag: Boolean;
begin
EnterCriticalSection(Mutex);
try
Result := ActiveFlag;
finally
LeaveCriticalSection(Mutex);
end;
end;

procedure TMyThread.SetActiveFlag(NewValue: Boolean);
begin
EnterCriticalSection(Mutex);
try
ActiveFlag := NewValue;
finally
LeaveCriticalSection(Mutex);
end;
end;

procedure TMyThread.Execute;
begin
SetActiveFlag(true);
DoWork();
SetActiveFlag(false);
end;

Also this method is not 100% safe, as we are setting ActiveFlag to 'false' right before leaving the Execute function. But internally the TThread object will not destroy the thread until Execute() has returned (and all callbacks have been called). So if the main thread checks IsActive and destroys the TThread object after ActiveFlag was set to 'false', but before the TThread object has actually destroyed the thread, we will get a problem! Consequently it might be better to override the 'DoTerminate' method:

type
TMyThread = class(TThread)
private
ActiveFlag: Boolean;
Mutex: TRTLCriticalSection;
function GetActiveFlag: Boolean;
procedure SetActiveFlag(NewValue: Boolean);
public
constructor Create(Suspended: Boolean);
destructor Destroy; override;
property IsActive: Boolean read GetActiveFlag;
protected
procedure Execute; override;
procedure DoTerminate; override;
end;

implementation

{ TMyThread }

constructor TMyThread.Create(Suspended: Boolean);
begin
inherited;
InitializeCriticalSection(Mutex);
ActiveFlag := true;
end;

destructor TMyThread.Destroy;
begin
inherited;
DeleteCriticalSection(Mutex);
end;

function TMyThread.GetActiveFlag: Boolean;
begin
EnterCriticalSection(Mutex);
try
Result := ActiveFlag;
finally
LeaveCriticalSection(Mutex);
end;
end;

procedure TMyThread.SetActiveFlag(NewValue: Boolean);
begin
EnterCriticalSection(Mutex);
try
ActiveFlag := NewValue;
finally
LeaveCriticalSection(Mutex);
end;
end;

procedure TMyThread.DoTerminate;
begin
inherited;
SetActiveFlag(false);
end;

procedure TMyThread.Execute;
begin
DoWork();
end;