Cannot terminate threads - multithreading

I use threads in my project. And I wanna kill and terminate a thread immediately.
sample:
type
test = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
var
Form1: TForm1;
a:tthread;
implementation
{$R *.dfm}
procedure test.Execute;
begin
Synchronize(procedure begin
form1.ProgressBar1.position := 0;
sleep(5000);
form1.ProgressBar1.position := 100;
end
);
end;
procedure TForm1.btn_startClick(Sender: TObject);
begin
a:=test.Create(false);
end;
procedure TForm1.btn_stopClick(Sender: TObject);
begin
terminatethread(a.ThreadID,1); //Force Terminate
end;
But when I click on the btn_stop (after clicking on btn_start), the thread won't stop. So how can stop this thread immediately?
BTW a.terminate; didn't work too.
Thanks.

This is a complete misuse of a worker thread. You are delegating all of the thread's work to the main thread, rendering the worker thread useless. You could have used a simple timer instead.
The correct use of a worker thread would look more like this instead:
type
test = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
var
Form1: TForm1;
a: test = nil;
implementation
{$R *.dfm}
procedure test.Execute;
var
I: integer
begin
Synchronize(
procedure begin
form1.ProgressBar1.Position := 0;
end
);
for I := 1 to 5 do
begin
if Terminated then Exit;
Sleep(1000);
if Terminated then Exit;
Synchronize(
procedure begin
Form1.ProgressBar1.Position := I * 20;
end
);
end;
Synchronize(
procedure begin
form1.ProgressBar1.Position := 100;
end
);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
btn_stopClick(nil);
end;
procedure TForm1.btn_startClick(Sender: TObject);
begin
if a = nil then
a := test.Create(False);
end;
procedure TForm1.btn_stopClick(Sender: TObject);
begin
if a = nil then Exit;
a.Terminate;
a.WaitFor;
FreeAndNil(a);
end;

The problem is the thread waits by using Sleep. This method will keep the thread sleeping for the specified time, no matter what happens around it. In order to be able to "break sleep" you should use an event. The code should be changed to this:
procedure test.Execute;
begin
Synchronize(procedure begin
form1.ProgressBar1.position := 0;
end);
Event.WaitFor(5000);
if not IsTerminated then
Synchronize(procedure begin
form1.ProgressBar1.position := 100;
end);
end;
The event should be created and destroyed like this:
constructor test.Create(aCreateSuspended: Boolean);
begin
inherited;
Event := TSimpleEvent.Create;
end;
destructor test.Destroy;
begin
FreeAndNil(Event);
inherited;
end;
In order to stop the thread, the code is:
procedure TForm1.btn_stopClick(Sender: TObject);
begin
a.Terminate;
end;
But simply calling Terminate won´t signal the Event, so we have to reimplement Terminate:
procedure test.Terminate;
begin
inherited;
Event.SetEvent;
end;
Calling SetEvent will signal the event, so it will wake the thread up. The execution continues in the next line, that tests for thread termination and decides to execute the second part of the code or not.

Related

CreateEvent multiple listeners

I'm trying to receive an event in multiple instances of my application.
For that purpose I've created a small demo program. First my TWorkerThread:
unit WorkerThreadU;
interface
uses
WinAPI.Windows, System.Classes;
type
TOnUpdate = reference to procedure(const Value: Integer);
TWorkerThread = class(TThread)
private
FUpdate: THandle;
FValue: Integer;
FResult: Integer;
FUpdateReady: TOnUpdate;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure Update;
property Value: Integer read FValue write FValue;
property OnUpdate: TOnUpdate read FUpdateReady write FUpdateReady;
end;
implementation
{ TWorkerThread }
constructor TWorkerThread.Create;
begin
inherited Create(False);
FUpdate := CreateEvent(nil, False, False, '{B2DCFF9B-ABF7-49BA-8B7C-4F63EF20D99E}');
end;
destructor TWorkerThread.Destroy;
begin
CloseHandle(FUpdate);
inherited;
end;
procedure TWorkerThread.Execute;
begin
while not Terminated do
begin
if WaitForSingleObject(FUpdate, 1000) <> WAIT_OBJECT_0 then
continue;
FResult := FValue * 2;
if not Assigned(FUpdateReady) then
continue;
TThread.Queue(nil,
procedure
begin
FUpdateReady(FResult);
end);
end;
end;
procedure TWorkerThread.Update;
begin
SetEvent(FUpdate);
end;
end.
My form:
...and the source for it:
procedure TfrmEvents.FormCreate(Sender: TObject);
begin
Caption := BoolToStr(Boolean(IsDebuggerPresent), True);
FWorkerThread := TWorkerThread.Create;
FWorkerThread.OnUpdate := procedure(const Value: Integer)
begin
Log(Format('2 * %d = %d', [inpValue.Value, Value]))
end;
end;
procedure TfrmEvents.btnCalcClick(Sender: TObject);
begin
try
FWorkerThread.Value := inpValue.Value;
Log('Calculating ...');
FWorkerThread.Update;
finally
end;
end;
procedure TfrmEvents.Log(const msg: string);
begin
lbLog.ItemIndex := lbLog.Items.Add(FormatDateTime('hh:nn:ss', Now) + ' ' + msg);
end;
My problem is that only one of the instances receives the event.
The program can also be found here.
This probably happens because CreateEvent uses the same name for all thread instances. That way all threads use the same event. As the event is created with automatic reset, the first thread getting the event will reset it and the others aren't noticed anymore.
From the docs:
If this parameter is FALSE, the function creates an auto-reset event
object, and system automatically resets the event state to nonsignaled
after a single waiting thread has been released.

Using an Anonymous Thread in Delphi to blink a label

I need to do a label to blink 5 times using a thread.
When I click on the button, I need the label blinks 5 times.
Now, I have a problem.
when I close the form I have a Memory Leak on Thread.
What am I doing wrong here?
type
TForm1= class(TForm)
...
labelNewMsg:Tlabel;
private
MEvent: TEvent;
procedure Torm1.FormCreate(Sender: TObject);
begin
MEvent := TEvent.Create(nil, False, False, '');
waitNewMessage();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MEvent.Free;
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
Mevent.SetEvent;
end;
procedure TForm1.waitNewMessage;
var
Status:TWaitResult;
begin
TThread.CreateAnonymousThread(
procedure
var IntCnt: Integer;
begin
while not TThread.CurrentThread.CheckTerminated and (not application.terminated) do begin
Sleep(100);
Status:=MEvent.WaitFor(INFINITE);
if Status=wrSignaled then begin
for IntCnt:=1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,procedure begin
labelNewMsg.Visible:=not labelNewMsg.Visible;
end);
end;
IntCnt:=0;
MEvent.ResetEvent;
end;
end;
end
).Start;
end;
Hi, I created a second option, but I have the same problem:
procedure TFrm_PrincipalDemo.waitNewMessage;
var
Status:TWaitResult;
begin
TThread.CreateAnonymousThread(
procedure
var IntCnt: Integer;
begin
while MEvent.WaitFor(INFINITE) in [wrSignaled] do begin
if TThread.CurrentThread.CheckTerminated then exit;
MEvent.ResetEvent;
Sleep(100);
for IntCnt:=1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,procedure begin
labelNewMsg.Visible:=not labelNewMsg.Visible;
end);
end;
if TThread.CurrentThread.CheckTerminated then exit;
end;
end
).Start;
end;
You are not signaling the thread to terminate itself before your Form is closed. For instance, if the thread is blocked waiting for MEvent, you need to signal MEvent so the thread can wake up and check for termination.
The Application.Terminated property is not set to True until the main message loop has processed a WM_QUIT message from PostQuitMessage(), which Application.Terminate() calls. The program's Application.MainForm calls Application.Terminate() when the Form is closed (not destroyed, that comes later).
If you keep a reference to the TThread object that you create, you can then call the TThread.Terminate() method directly, which sets the thread's Terminated property to True (otherwise, there is no point in calling TThread.CheckTerminated() inside of the thread at all), eg:
type
TForm1 = class(TForm)
...
labelNewMsg: TLabel;
ButtonDoSetEvent: TButton;
...
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; Action: TCloseAction);
procedure FormDestroy(Sender: TObject);
procedure ButtonDoSetEventClick(Sender: TObject);
...
private
MEvent: TEvent;
Thread: TThread;
procedure waitNewMessage;
procedure ThreadTerminated(Sender: TObject);
...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MEvent := TEvent.Create(nil, False, False, '');
waitNewMessage();
end;
procedure TForm1.FormClose(Sender: TObject; Action: TCloseAction);
begin
if Thread <> nil then
begin
Thread.Terminate;
MEvent.SetEvent;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Thread <> nil then
Thread.OnTerminate := nil;
MEvent.Free;
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
MEvent.SetEvent;
end;
procedure TForm1.waitNewMessage;
begin
Thread := TThread.CreateAnonymousThread(
procedure
var
IntCnt: Integer;
Status: TWaitResult;
begin
while not TThread.CheckTerminated do begin
Sleep(100);
Status := MEvent.WaitFor(INFINITE);
if (Status = wrSignaled) and (not TThread.CheckTerminated) then begin
for IntCnt := 1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,
procedure
begin
labelNewMsg.Visible := not labelNewMsg.Visible;
end
);
end;
end;
end;
end
);
Thread.OnTerminate := ThreadTerminated;
Thread.Start;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
Thread := nil;
end;
But really, why are you even using a thread at all? Nothing your thread does actually needs to be in a thread in the first place. A simple timer would suffice instead, and it would be safer for the UI, and easier to stop during program shutdown.
type
TForm1 = class(TForm)
...
labelNewMsg: TLabel;
ButtonDoSetEvent: TButton;
NewMsgTimer: TTimer;
...
procedure ButtonDoSetEventClick(Sender: TObject);
procedure NewMsgTimerTimer(Sender: TObject);
...
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
NewMsgTimer.Tag := 0;
NewMsgTimer.Enabled := True;
end;
procedure TForm1.NewMsgTimerTimer(Sender: TObject);
begin
NewMsgTimer.Tag := NewMsgTimer.Tag + 1;
labelNewMsg.Visible := not labelNewMsg.Visible;
if NewMsgTimer.Tag = 5 then
NewMsgTimer.Enabled := False;
end;

Delphi - Multithreading: Why I can't start thread again after thread.terminate()?

I've coding a multithread application that send and receive TCP packages. I'm with the problem that when I call twice event confirmBoxRecognized(peerIP: string) of the code bellow. I'm getting the following exception:
Cannot call Start on a running or suspended thread
If I check in the thread object I've that terminated == true and suspended == false. Why am I coding wrong?
Following the code:
TThreadReadTCP = class(TThread)
private
context: TfrmBoxTest;
looping: Boolean;
procedure readTCP;
protected
procedure DoTerminate; override;
procedure Execute; override;
public
peerIP: String;
responseObject: TProtocolObject;
constructor Create(CreateSuspended: Boolean; ctx: TFrmBoxTest); overload;
end;
{ TThreadReadTCP }
constructor TThreadReadTCP.Create(CreateSuspended: Boolean; ctx: TFrmBoxTest);
begin
inherited Create(CreateSuspended);
Self.context := ctx;
FreeOnTerminate := True;
end;
procedure TThreadReadTCP.DoTerminate;
begin
looping := false;
inherited DoTerminate();
end;
procedure TThreadReadTCP.Execute;
begin
inherited;
looping := true;
readTCP;
end;
procedure TThreadReadTCP.readTCP;
var
buffer: TBytes;
begin
while looping do
begin
if context.tcpClientBox.Connected then
begin
try
buffer := TEncoding.ASCII.GetBytes(context.tcpClientBox.Socket.ReadLn());
//do something else
except on E:Exception do
ShowMessage('Error receiving TCP buffer with message: ' + e.Message);
end;
end;
end;
end;
procedure TfrmBoxTest.confirmBoxRecognized(peerIP: string);
begin
if (connectBoxTCP(peerIP)) then
begin
if Assigned(threadReadTCP) then
begin
threadReadTCP.Terminate();
threadReadTCP.Start(); // I get the exception here when I run this code twice...
end;
showBoxRecognized();
end;
sendBoxRecognized();
end;
Are there running thread status can I get? Or anyone can explain how can I improve this code to solve this problem?
Thanks a lot!
You get the exception because you can only call Start() on a TThread object one time. Once the thread has been started, you cannot restart it. Once it has been signaled to terminate, all you can do is wait for it to finish terminating, and then destroy the object.
If you want another thread to start running, you have to create a new TThread object, eg:
type
TThreadReadTCP = class(TThread)
private
context: TfrmBoxTest;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
peerIP: String;
responseObject: TProtocolObject;
constructor Create(ctx: TFrmBoxTest); reintroduce;
end;
constructor TThreadReadTCP.Create(ctx: TFrmBoxTest);
begin
inherited Create(False);
Self.context := ctx;
// NEVER use FreeOnTerminate=True with a thread object that you keep a reference to!
// FreeOnTerminate := True;
end;
procedure TThreadReadTCP.Execute;
var
buffer: TBytes;
begin
while not Terminated do
begin
try
buffer := TEncoding.ASCII.GetBytes(context.tcpClientBox.Socket.ReadLn());
// do something else
except
on E: Exception do
begin
// do something
raise;
end;
end;
end;
end;
procedure TThreadReadTCP.TerminatedSet;
begin
try
context.tcpClientBox.Disconnect(False);
except
end;
end;
...
procedure TfrmBoxTest.confirmBoxRecognized(peerIP: string);
begin
if Assigned(threadReadTCP) then
begin
threadReadTCP.Terminate();
threadReadTCP.WaitFor();
FreeAndNil(threadReadTCP);
end;
if connectBoxTCP(peerIP) then
begin
threadReadTCP := TThreadReadTCP.Create(Self);
showBoxRecognized();
end;
sendBoxRecognized();
end;

Multithreading and MessageDlgPos

Hi I'm doing a code MessageDlgPos running five threads at the same time, the code is this:
type
TMyThread = class(TThread)
protected
procedure Execute; override;
public
text: string;
property ReturnValue;
end;
procedure TMyThread.Execute;
begin
if Terminated then
Exit;
MessageDlgPos(text, mtInformation, [mbOk], 0, 100, 200);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
LThread: TMyThread;
i: Integer;
begin
For i := 1 to 5 do
begin
LThread := TMyThread(Sender);
try
LThread.text := 'hi';
LThread.FreeOnTerminate := True;
except
LThread.Free;
raise;
end;
LThread.Resume;
end;
end;
The problem is that Delphi XE always returns the following error and does not execute anything:
First chance exception at $ 7524B727. Exception class EAccessViolation with message 'Access violation at address 00D0B9AB. Write of address 8CC38309 '. Process tester.exe (6300)
How do I fix this problem?
As David Heffernan pointed out, MessageDlgPos() cannot safely be called outside of the main UI thread, and you are not managing the thread correctly. Your code needs to look more like this instead:
type
TMyThread = class(TThread)
protected
procedure Execute; override;
public
text: string;
property ReturnValue;
end;
procedure TMyThread.Execute;
begin
// no need to check Terminated here, TThread already
// does that before calling Execute()...
TThread.Synchronize(nil,
procedure
begin
MessageDlgPos(text, mtInformation, [mbOk], 0, 100, 200);
end
);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
LThread: TMyThread;
i: Integer;
begin
For i := 1 to 5 do
begin
LThread := TMyThread.Create(True);
LThread.text := 'hi';
LThread.FreeOnTerminate := True;
LThread.Start;
end;
end;
I would suggest a slightly different variation:
type
TMyThread = class(TThread)
private
fText: string;
protected
procedure Execute; override;
public
constructor Create(const aText: string); reintroduce;
property ReturnValue;
end;
constructor TMyThread.Create(const aText: string);
begin
inherited Create(False);
FreeOnTerminate := True;
fText := aText;
end;
procedure TMyThread.Execute;
begin
// no need to check Terminated here, TThread already
// does that before calling Execute()...
TThread.Synchronize(nil,
procedure
begin
MessageDlgPos(fText, mtInformation, [mbOk], 0, 100, 200);
end
);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
i: Integer;
begin
For i := 1 to 5 do
begin
TMyThread.Create('hi');
end;
end;
But either way, if you don't like using TThread.Synchronize() to delegate to the main thread (thus only displaying 1 dialog at a time) then you cannot use MessageDlgPos() at all, since it is only safe to call in the main UI thread. You can use Windows.MessageBox() instead, which can be safely called in a worker thread without delegation (but then you lose the ability to specify its screen position, unless you access its HWND directly by using a thread-local hook via SetWindowsHookEx() to intercept the dialog's creation and discover its HWND):
procedure TMyThread.Execute;
begin
Windows.MessageBox(0, PChar(fText), PChar(Application.Title), MB_OK or MB_ICONINFORMATION);
);
end;
There are many problems. The biggest one is here:
LThread := TMyThread(Sender);
Sender is a button. Casting to a thread is simply wrong and the cause of your exception. Casting a button to a thread doesn't make it so. It's still a button.
You likely mean to create a thread instead.
LThread := TMyThread.Create(True);
You cannot show VCL UI outside the main thread. The call to MessageDlgPos breaks that rule. If you do need to show UI at that point, you'll need to use TThread.Synchronize to have the code execute in the main thread.
Your exception handler makes no sense to me. I think you should remove it.
Resume is deprecated. Use Start instead.

Thread destroy never gets executed

Download the source code with compiled executable (221 KB (226,925 bytes)): http://www.eyeclaxton.com/download/delphi/skeleton.zip
Why doesn't the Destroy destructor get called if I close the application (click the X button) before the thread has terminated? FastMM4 reports a memory leak with FPauseEvent event.
How should i destroy thread? If someone closes the application before the thread finishes.
unit SkeletonThread;
interface
uses
Windows, Classes, SysUtils, SyncObjs;
type
TOnInitialize = procedure(Sender: TObject; const AMaxValue: Integer) of object;
TOnBegin = procedure(Sender: TObject) of object;
TOnProgress = procedure(Sender: TObject; const APosition: Integer) of object;
TOnPause = procedure(Sender: TObject; const APaused: Boolean) of object;
TOnFinish = procedure(Sender: TObject) of object;
TOnFinalize = procedure(Sender: TObject) of object;
TMasterThread = class(TThread)
private
{ Private declarations }
FPaused: Boolean;
FPosition: Integer;
FMaxValue: Integer;
FOnBegin: TOnBegin;
FOnProgress: TOnProgress;
FOnFinish: TOnFinish;
FOnInitialize: TOnInitialize;
FOnFinalize: TOnFinalize;
FPauseEvent: TEvent;
FOnPause: TOnPause;
procedure BeginEvent();
procedure ProgressEvent();
procedure FinishEvent();
procedure InitializeEvent();
procedure FinalizeEvent();
procedure PauseEvent();
procedure CheckForPause();
protected
{ Protected declarations }
procedure DoInitializeEvent(const AMaxValue: Integer); virtual;
procedure DoBeginEvent(); virtual;
procedure DoProgress(const APosition: Integer); virtual;
procedure DoPauseEvent(const APaused: Boolean); virtual;
procedure DoFinishEvent(); virtual;
procedure DoFinalizeEvent(); virtual;
public
{ Public declarations }
constructor Create(const CreateSuspended: Boolean; const theValue: Integer);
destructor Destroy(); override;
procedure Pause();
procedure Unpause();
published
{ Published declarations }
property IsPaused: Boolean read FPaused write FPaused default False;
property OnInitialize: TOnInitialize read FOnInitialize write FOnInitialize default nil;
property OnBegin: TOnBegin read FOnBegin write FOnBegin default nil;
property OnProgress: TOnProgress read FOnProgress write FOnProgress default nil;
property OnPause: TOnPause read FOnPause write FOnPause default nil;
property OnFinish: TOnFinish read FOnFinish write FOnFinish default nil;
property OnFinalize: TOnFinalize read FOnFinalize write FOnFinalize default nil;
end;
TSkeletonThread = class(TMasterThread)
private
{ Private declarations }
procedure DoExecute(const theValue: Integer);
protected
{ Protected declarations }
procedure Execute(); override;
public
{ Public declarations }
published
{ Published declarations }
end;
implementation
{ TMasterThread }
constructor TMasterThread.Create(const CreateSuspended: Boolean; const theValue: Integer);
begin
inherited Create(CreateSuspended);
Self.FreeOnTerminate := True;
Self.FPosition := 0;
Self.FMaxValue := theValue;
Self.FPaused := False;
Self.FPauseEvent := TEvent.Create(nil, True, True, '');
end;
destructor TMasterThread.Destroy();
begin
FreeAndNil(FPauseEvent);
if (Pointer(FPauseEvent) <> nil) then Pointer(FPauseEvent) := nil;
inherited Destroy();
end;
procedure TMasterThread.DoBeginEvent();
begin
if Assigned(Self.FOnBegin) then Self.FOnBegin(Self);
end;
procedure TMasterThread.BeginEvent();
begin
Self.DoBeginEvent();
end;
procedure TMasterThread.DoProgress(const APosition: Integer);
begin
if Assigned(Self.FOnProgress) then Self.FOnProgress(Self, APosition);
end;
procedure TMasterThread.ProgressEvent();
begin
Self.DoProgress(Self.FPosition);
end;
procedure TMasterThread.DoFinishEvent();
begin
if Assigned(Self.FOnFinish) then Self.FOnFinish(Self);
end;
procedure TMasterThread.FinishEvent();
begin
Self.DoFinishEvent();
end;
procedure TMasterThread.DoInitializeEvent(const AMaxValue: Integer);
begin
if Assigned(Self.FOnInitialize) then Self.FOnInitialize(Self, AMaxValue);
end;
procedure TMasterThread.InitializeEvent();
begin
Self.DoInitializeEvent(Self.FMaxValue);
end;
procedure TMasterThread.DoFinalizeEvent();
begin
if Assigned(Self.FOnFinalize) then Self.FOnFinalize(Self);
end;
procedure TMasterThread.FinalizeEvent;
begin
Self.DoFinalizeEvent();
end;
procedure TMasterThread.DoPauseEvent(const APaused: Boolean);
begin
if Assigned(Self.FOnPause) then Self.FOnPause(Self, APaused);
end;
procedure TMasterThread.PauseEvent();
begin
Self.DoPauseEvent(Self.FPaused);
end;
procedure TMasterThread.Pause();
begin
Self.FPauseEvent.ResetEvent();
Self.FPaused := True;
Self.Synchronize(Self.PauseEvent);
end;
procedure TMasterThread.Unpause();
begin
Self.FPaused := False;
Self.Synchronize(Self.PauseEvent);
Self.FPauseEvent.SetEvent();
end;
procedure TMasterThread.CheckForPause();
begin
if (not (Self.Terminated)) then Windows.Sleep(1);
Self.FPauseEvent.WaitFor(INFINITE);
end;
{ TSkeletonThread }
procedure TSkeletonThread.DoExecute(const theValue: Integer);
var
X: Integer;
begin
Self.Synchronize(InitializeEvent);
try
Self.Synchronize(BeginEvent);
try
for X := 0 to (theValue - 1) do
begin
Self.CheckForPause();
if (not Self.FPaused) and (not Self.Terminated) then
begin
Self.FPosition := Self.FPosition + 1;
Self.Synchronize(ProgressEvent);
end
else begin
Break;
end;
end;
for X := Self.FPosition downto 1 do
begin
Self.CheckForPause();
if (not Self.FPaused) and (not Self.Terminated) then
begin
Self.FPosition := X;
Self.Synchronize(ProgressEvent);
end
else begin
Break;
end;
end;
finally
Self.Synchronize(FinishEvent);
end;
finally
Self.Synchronize(FinalizeEvent);
end;
end;
procedure TSkeletonThread.Execute();
begin
Self.DoExecute(Self.FMaxValue);
end;
end.
You have to terminate the thread yourself (tell it to stop). One way is to use the Terminate procedure of the thread, but you have to check for this in the thread Execute method. Something like this:
procedure Execute;
begin
inherited;
while not Terminated do
begin
// do your job
end;
end;
procedure TForm1.StopThread;
begin
MyThread.Terminate;
// wait and block until the scheduling thread is finished
AResult := WaitForSingleObject(MyThread.Handle, cShutdownTimeout);
// check if we timed out
if AResult = WAIT_TIMEOUT then
TerminateThread(MyThread.Handle, 0);
end;
Or you can use signalization build into windows so you do not have to loop.
procedure Execute;
begin
inherited;
while not Terminated do
begin
WaitStatus := WaitForSingleObject(FTermEvent, Max(0, SleepInterval));
// check what was the cause for signalization
if WaitStatus <> WAIT_TIMEOUT then
Terminate;
end;
end;
procedure TForm1.StopThread;
begin
// Terminate the thread
SetEvent(FTermEvent);
// close the handle
CloseHandle(FTermEvent);
// wait and block until the scheduling thread is finished
AResult := WaitForSingleObject(MyThread.Handle, cShutdownTimeout);
// check if we timed out
if AResult = WAIT_TIMEOUT then
TerminateThread(MyThread.Handle, 0);
end;
Signalization can be very neat way of signaling for termination because you can use WaitForMultipleObjects and release the wait in different conditions. I used WaitForSingleObject to not complicate things to much.
Also be sure to set "FreeOnTerminate := True" in thread constructor. Oh and the hard termination at the end is optional of course. It can be dangerous. You know best yourself if you will use it or not. You can also wait for a longer period or infinite if you are sure the thread will stop eventually.

Resources