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.
Related
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.
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;
I'm using Delphi XE6.
I have a thread where I pass a ID and would like to get back a string created by the thread. I looked at all examples, but they all getting values back when thread is running I just need it OnTerminate.
Calling the thread from a form:
StringReturnedFromThread := PrintThread.Create(MacId);
PrintThread = class(TThread)
private
MyReturnStr, PrinterMacId : String;
public
constructor Create(MacId: String); reintroduce;
procedure OnThreadTerminate(Sender: TObject);
protected
procedure Execute; override;
end;
constructor PrintThread.Create(MacId: String);
begin
inherited Create(False);
OnTerminate := OnThreadTerminate;
FreeOnTerminate := True;
PrinterMacId := MacId;
end;
procedure PrintThread.Execute;
begin
PrepareConnection;
MyReturnStr:= RequestPrintJobs(PrinterMacId);
end;
procedure PrintThread.OnThreadTerminate(Sender: TObject);
begin
end;
Thanks for any help.
You need to intercept thread termination. One way is to use TThread.OnTerminate event/callback.
Below a sample code.
Thread unit:
unit Processes;
interface
uses
System.Classes;
type
TProcess = class(TThread)
private
FReturnStr: string;
FMacId: string;
protected
procedure Execute; override;
public
property MacId: string read FMacId write FMacId;
property ReturnStr: string read FReturnStr write FReturnStr;
constructor Create;
end;
implementation
constructor TProcess.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
end;
procedure TProcess.Execute;
begin
// Some hard calculation here
FReturnStr := FMacId + 'BLA';
end;
end.
Thread usage:
uses Processes;
procedure TForm1.Button1Click(Sender: TObject);
var P: TProcess;
begin
// Create the thread
P := TProcess.Create;
// Initialize it
P.MacId := 'MID123';
// Callback handler
P.OnTerminate := OnProcessTerminate;
// Let's go
P.Start;
end;
procedure TForm1.OnProcessTerminate(Sender: TObject);
var P: TProcess;
begin
// The thread has been terminated
P := TProcess(Sender);
ShowMessage(P.ReturnStr);
end;
The thread will return MID123BLA on it's termination.
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.
I'm trying to put an indy TIdHttp in a thread,
I have tried this :
type
TSendThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
public
http : TIdHTTP;
URL : String;
Method : String;
property ReturnValue;
end;
procedure TSendThread.Execute;
begin
form1.Memo1.lines.Add(http.Get(URL));
ReturnValue := 1;
end;
And in the main :
procedure TForm1.Button1Click(Sender: TObject);
var t : TSendThread;
begin
t := TSendThread.Create(true);
t.URL := 'http://www.url.com/';
t.http := http;
t.Start;
showmessage(IntToStr(t.ReturnValue));
end;
My problem here is that the next instruction gets executed(showmessage) without waiting the thread to be done, i tried to use the "WaitFor" but it freezes the application.
Is there any other workaround?
Thank you.
Use the TThread.OnTerminate event to know when the thread has finished:
type
TSendThread = class(TThread)
private
http : TIdHTTP;
Line: string;
procedure AddLine;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
URL : String;
Method : String;
property ReturnValue;
end;
constructor TSendThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
http := TIdHTTP.Create;
end;
destructor TSendThread.Destroy;
begin
http.Free;
inherited;
end;
procedure TSendThread.Execute;
begin
Line := http.Get(URL);
Synchronize(AddLine);
ReturnValue := 1;
end;
procedure TSendThread.AddLine;
begin
Form1.Memo1.Lines.Add(Line);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.Create;
t.URL := 'http://www.url.com/';
t.OnTerminate := ThreadTerminated;
t.Start;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
ShowMessage(IntToStr(TSendThread(Sender).ReturnValue));
end;
If you want to use a loop to wait for the thread to finish, without blocking the UI, then you can do it like this:
constructor TSendThread.Create;
begin
inherited Create(True);
//FreeOnTerminate := True; // <-- remove this
http := TIdHTTP.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
h : THandle;
begin
t := TSendThread.Create;
try
t.URL := 'http://www.url.com/';
t.Start;
h := t.Handle;
repeat
case MsgWaitForMultipleObjects(1, h, 0, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0: Break;
WAIT_OBJECT_0+1: Application.ProcessMessages;
WAIT_FAILED: RaiseLastOSError;
else
Break;
end;
until False;
ShowMessage(IntToStr(t.ReturnValue));
finally
t.Free;
end;
end;