I have a thread called TMyThread and I overrode the Execute procedure like this:
procedure TMyThread.Execute;
Begin
repeat
//Some Work
Sleep(5000);
//Some Work 2;
Sleep(5000);
until FActive=False
End;
In the main form I have button called 'Destroy My Thread'. I want to destroy my thread but the problem is that my thread will be destroyed only if it finish its work. I want to destroy my thread even it has not finished its work. How do I do this?
Your thread's Execute method must regularly check the state of the thread's Terminated property. And if it is True, then the thread Execute method must exit.
So, a typical Execute method might look like this:
procedure TMyThread.Execute;
begin
while not Terminated do
DoNextPieceOfWork
end;
It looks like your thread has its own FActive flag that is performing the same task. The problem with that is that TThread doesn't know about it. So you should get rid of FActive and instead use the built in mechanism.
When you call Free on the thread it will call Terminate. That sets Terminated to be True. Then it waits for the thread method to exit. That will happen because your thread notices that Terminated is True and quits. And then the thread's destructor can continue and finish the job of tidying up the thread.
Looking at the code in your answer, it would be better written to make use of the existing Terminate mechanism.
type
TMyThread = class(TThread)
private
FTerminateEvent: TEvent;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
constructor Create(ACreateSuspended: Boolean);
destructor Destroy; override;
end;
constructor TMyThread.Create(ACreateSuspended: Boolean);
begin
inherited Create(ACreateSuspended);
FTerminateEvent := TEvent.Create(nil, True, False, '');
end;
destructor TMyThread.Destroy;
begin
inherited;
FTerminateEvent.Free;
end;
procedure TMyThread.TerminatedSet;
begin
FTerminateEvent.SetEvent;
end;
procedure TMyThread.Execute;
begin
while not Terminated do
begin
// do somthing interesting!
FTerminateEvent.WaitFor(5000);
end;
end;
Now there's no need for a separate Stop method. You can just call Free on the thread. Then Terminate is called. And then TerminatedSet is called. Then the event is signaled. Then Execute terminates. And then the thread can go away.
Having said that, I'm struggling to think of a scenario where a 5000ms timeout would be the best approach. I don't know why you are doing this, but I'd guess that you are trying to throttle the thread so that it doesn't run hot. You want to avoid a busy loop. That's admirable, but using a fixed timeout is not the way to do it. The way to do it is to wait on a synchronisation event, typically an event. Then when there is more work to be done, the event becomes signaled and your thread wakes up.
Using TEvent can solve this problem
this is an eg :
uses SyncObjs;
TMyThread = class(TThread)
private
FTerminateEvent: TEvent;
protected
procedure Execute; override ;
public
constructor Create(ACreateSuspended: Boolean); overload;
destructor Destroy; override;
procedure Stop;
end;
constructor TMyThread.Create(ACreateSuspended: Boolean);
begin
FTerminateEvent := TEvent.Create(nil, True, False, 'FTerminateEvent');
inherited Create(ACreateSuspended);
end;
destructor TMyThread.Destroy;
begin
FTerminateEvent.Free;
inherited;
end;
procedure TMyThread.Stop;
begin
Terminate;
FTerminateEvent.SetEvent;
end;
procedure TMyThread.Execute;
begin
while not Terminated do
begin
// do somthing interesting!
FTerminateEvent.WaitFor(5000);
end;
end;
Now if i want to kill my thread ,all what i have to do is calling MyThread.Stop than calling MyThread.Free .
You really are approaching this problem completely wrong. Code your threads to only do work that you want them to do, and then there will be no need to "reach in from the outside" to control them. You feel the need to control this thread from the outside because it's doing something you don't want it to do -- well then why did you code it to do that in the first place?
Related
The Delphi help for TThread.OnTerminate states that:
The method assigned to the OnTerminate event is executed in the context of the main thread rather than the context of the thread being terminated.
Is this even the case when the thread is created in another thread than the main thread?
So, is the OnTerminate called in the thread that created the TThread, or is it called in main thread? The IDE does not tells me this. When I debug, I see no active thread in the OnTerminate event. :-/
The documentation is correct. The OnTerminate event handler is always run in the main thread by default. Internally, TThread.DoTerminate() (which is called after the thread's Execute() method exits) uses TThread.Synchronize() to call the handler:
function ThreadProc(Thread: TThread): Integer;
var
...
begin
...
try
if not Thread.Terminated then
try
Thread.Execute;
except
...
end;
finally
...
Thread.DoTerminate;
...
end;
end;
procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;
procedure TThread.CallOnTerminate;
begin
if Assigned(FOnTerminate) then FOnTerminate(Self);
end;
If you want the OnTerminate handler to run in the context of the terminating thread (or any other thread you want), you can simply override DoTerminate() to call the handler however you want, eg:
type
TMyThread = class(TThread)
...
protected
...
procedure Execute; override;
procedure DoTerminate; override;
...
end;
procedure TMyThread.Execute;
begin
...
end;
procedure TMyThread.DoTerminate;
begin
// do whatever you want here, but DON'T call inherited!
if Assigned(OnTerminate) then OnTerminate(Self);
end;
We have an application in which the user can talk to us, it works fine, he create a new conversation, we chat, and that's ok. But, before start chatting, he needs to connect to the DataSnap Server, and that's where I'm trying to make a Thread. Every 5min, a timer would trigger his event to create the Thread and try to connect on the server, as below:
My Thread:
unit UThreadSnapConnection;
interface
uses
System.Classes, System.SysUtils, Data.SqlExpr;
type
TThreadSnapConnection = class(TThread)
private
FSnap: TSQLConnection;
procedure TryToConnect;
protected
procedure Execute; override;
constructor Create;
public
DMSnap: TSQLConnection;
HostName: String;
Port: String;
end;
implementation
{ TThreadSnapConnection }
constructor TThreadSnapConnection.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
end;
procedure TThreadSnapConnection.TryToConnect;
begin
try
FSnap := DMSnap.CloneConnection;
FSnap.Connected := False;
try
FSnap.Connected := True;
except
end;
if FSnap.Connected then
DMSnap.Connected := True;
finally
FreeAndNil(FSnap);
end;
end;
procedure TThreadSnapConnection.Execute;
begin
Synchronize(TryToConnect);
end;
end.
My Timer:
procedure TMyDataModuleSnap.TimerSnapTimer(Sender: TObject);
var
MyThread: TThreadSnapConnection;
begin
if not(MySQLConnection.Connected) then
begin
MyThread := TThreadSnapConnection.Create;
MyThread.DMSnap := MySQLConnection;
MyThread.HostName := 'localhost';
MyThread.Port := '211';
MyThread.Resume;
end;
end;
What I'm doing is an attempt to connect to the server, if it works, then it will make my data module connect.
My problem is, everytime the line
FSnap.Connected := True;
execute it freezes for 1~2 seconds the application, and the reason I made a thread was to not freeze. As long as I know, it should not bother at all the application, so I started to think maybe it's the work it does when setting the Connected property to True, which will freeze independent if it's thread or not.
Is there any way to not freeze when trying to connect?
And this is my first thread and maybe I just misunderstood things and that's not how thread works, but well, if it is not then I need to know, or at least understand what I'm doing wrong with it.
EDIT: The test I'm doing is, I start the application without starting the server, so it will try to connect unsuccessful, and my data module will not connect too.
There are two options:
while the OnTimer event of a TTimer is executed in the thread which has created the timer, you may consider to create the instance outside the main thread
you may consider to use a TThread class instance
The following applies to the #2.
Using a TEvent in the Execute procedure of your thread you can wait for an amount of FInterval time before the execution of the next block of code.
When the Terminated property is set to True, this approach allows the Execute method to immediately return also during the interval count unlike the adoption of a TThread.Sleep(FInterval); call which would freeze the thread itself for the amount of time specified.
The main thread can be optionally notified using a TNotifyEvent when done.
TMyThread = class(TThread)
private
FInterval: Integer;
FTerminateEvent: TEvent;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
OnEndJob: TNotifyEvent;
constructor Create(Interval: Cardinal; CreateSuspended: Boolean);
destructor Destroy; override;
end;
constructor TMyThread.Create(Interval: Cardinal; CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FInterval := Interval;
FTerminateEvent := TEvent.Create(nil, False, False, '');
end;
destructor TMyThread.Destroy;
begin
FTerminateEvent.Free;
inherited;
end;
procedure TMyThread.TerminatedSet;
begin
inherited;
FTerminateEvent.SetEvent;
end
procedure TMyThread.Execute;
begin
while not Terminated do begin
//do your stuff
//notify your connection to the main thread if you want
if Assigned(OnEndJob) then
Synchronize(procedure
begin
OnEndJob(Self);
end);
//wait fo some amount of time before continue the execution
if wrSignaled = FterminateEvent.WaitFor(FInterval) then
Break;
end;
end;
Don't synchonize the code you want to be executed in a thread: in Delphi a syncronized block is always executed in the calling thread.
I would have preferred to post a comment rather than an answer, but lack the reputation points; something worth considering when reading the following.
Reading between the lines, it looks like you have a connection to a local SQL server. Access is infrequent causing the connection to drop, so you've instituted a timer to check every 5 mins and re-establish the connection if necessary.
This worked, but you found that the connection attempt blocks program execution until it is established, and so you want to move this operation to a worker thread.
As stated by fantaghirocco, Synchronize causes code to run within the main program thread. My understanding is this code runs after all messages in the main thread have been processed, so you could achieve the same result by having the timer post a message, and the associated message handler call TryToConnect (TryToConnect declared in the main form in this case).
Synchronize is the easiest means of allowing threads to interact with the main thread without having to worry about two or more threads accessing the same object at the same time.
To prevent the connection process from blocking the main program thread, the MySQLConnection Connected property would have to be set in the Execute method of the TThread descendant (not encapsulated within a call to Synchronize).
But doing so introduces the risk of the worker thread and main program accessing MySQLConnection at the same time. To protect against this you would need to introduce a critical section, or similar. If unfamiliar, then check TCriticalSection in the RAD Studio help; there's a section on Critical Sections and an example.
Both the main program and thread would then encapsulate any calls to MySQLConnection within a critical section try finally block:
FLock.Acquire;
try
{code accessing MySQLConnection goes here}
finally
FLock.Release;
end;
Where FLock is a TCriticalSection object.
Any thread attempting to acquire FLock while already acquired by another, will be blocked until FLock is released. This means the main thread would only be blocked if the user attempted access to MySQLConnection when the worker thread was already attempting a connection.
Update:
To get you started, the following is a simple program consisting of two units; Unit1 contains the main form (what you're presented with when you create a new application). The second unit, Unit2 contains a thread. I've done it this way since your thread appears to be in a separate unit.
I've added a button and a critical section to TForm1 (add System.SyncObjs to the uses clause). In the click event of Button1 I create an instance of TMyThread (in your code this would be handled by the timer event):
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FLock: TCriticalSection;
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyThread.Create;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FLock := TCriticalSection.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FLock.Free;
end;
Unit2 contains the thread. The execute method is a fire once and finish effort. Unit1 is added to the uses clause in the implementation to give the code access to the Form1 variable:
type
TMyThread = class (TThread)
protected
procedure Execute; override;
public
constructor Create;
end;
implementation
uses Unit1;
{ TMyThread }
constructor TMyThread.Create;
begin
inherited Create (False);
end;
procedure TMyThread.Execute;
begin
with Form1 do begin
FLock.Acquire;
try
{access MySQLConnection methods here}
finally
FLock.Release;
end;
end;
end;
When you run this simple program and click Button1, a separate thread is created and the execute method run, following which the thread is destroyed. This process is repeated every time you click Button1.
If you place a breakpoint in Unit1 on the MyThread := TMyThread.Create line, and another breakpoint in Unit2 on the FLock.Acquire line, run the program and click Button1, the code will stop in the main thread; the thread Id shown in the left hand pane.
If you click F9 to continue program execution, it will then stop on the Unit2 breakpoint. You'll note the thread Id is now different, and the Thread Status window down the bottom of the IDE now lists this extra thread. When you press F9 again and this new thread disappears.
This program does nothing, but you would place whatever MySQLConnection code you needed to run in this thread where I have the comment in the Try Finally block.
In the main thread, wherever MySQLConnection's methods are accessed, you will also need to encapsulate these within a FLock try finally block. For example, if you have a TClientDataSet connected to a TDataSetProvider connected to a TSQLDataSet connected to your MySQLConnection, then opening the TClientDataSet will have to be encapsulated within this FLock Try Finally:
begin
FLock.Acquire;
try
CDS.Open;
finally
FLock.Release;
end;
end;
Where CDS is the TClientDataSet.
The code you intend to run in the thread basically closes the connection and re-opens it. A side benefit of the critical section (if properly configured, and all access to MySQLConnection protected by the critical section), is it will prevent the connection being shut in the middle of a user's query.
I have a TButton in the main TForm. When user click the button, it will execute the below process:
begin
Process_done := FALSE;
Process_Result.Clear;
cmdProcess.CommandLine := #34+AppPath+'getdata.exe"';
cmdProcess.Run;
Repeat
Application.ProcessMessages;
Until Process_done;
end;
As you can see above, the process calls external executable, and the process can take some times which blocking the main application.
This is only one process, and I need another one.
So, I am thinking to implement multi-threading, where I can run the above process in a separate thread. The other process as well. And the main thread can do something WHILE checking when both processes done.
Can anyone give me some examples how to do this using Delphi 7?
OR point me to an article, simple implementation like this?
Thanks.
Try something like this:
type
TRunProcessThread = class(TThread)
protected
cmdProcess: Whatever;
procedure Execute; override;
public
constructor Create(const ACmdLine: String);
destructor Destroy; override;
end;
constructor TRunProcessThread.Create(const ACmdLine: String);
begin
inherited Create(True);
FreeOnTerminate := True;
cmdProcess := Whatever.Create;
cmdProcess.CommandLine := ACmdLine;
end;
destructor TRunProcessThread.Destroy;
begin
cmdProcess.Free;
inherited;
end;
procedure TRunProcessThread.Execute;
begin
cmdProcess.Run;
...
end;
.
procedure TForm1.Button1Click(Sender: TObject);
var
Thread: TRunProcessThread;
begin
Thread := TRunProcessThread.Create(AnsiQuotedStr(AppPath + 'getdata.exe', #34));
Thread.OnTerminate := ProcessDone;
Thread.Resume;
end;
procedure TForm1.ProcessDone(Sender: TObject);
begin
// access TRunProcessThread(Sender) to get result information as needed ...
end;
You should create a class inherited from TThread and put that code in there. I don't remember exactly, but I think you'll find TThread template in File->New dialog box. When code execution is finished, you just notify your gui. Here's an article how to synchronize UI with external thread http://delphi.about.com/od/kbthread/a/thread-gui.htm
I have created a class that derives from TThread, because I wish to do some async stuff, however to avoid having to create another class, I built the entire thing around that thread class. Not sure if this is good practice or not, and if I cant get this to work, well then I suppose I have no choice but to recode..
The problem: I create the Thread on FormCreate, assign some properties, and I Free it on FormDestroy. In the Thread's constructor, I set FreeOnTerminate = False. When I click on a button on my Form, I Start(); the Thread. Okay, so it runs as expected, an error occurs (expected!), its being passed to my error handling event, and it appears to terminate. I then click the button again, and I get a Cannot call Start on a running or suspended thread error.
How can I finish the thread without freeing it, and enabling me to start it again?
You can't restart a thread once it is finished/terminated. In that case you should just create a new instance if the thread again like you did in FormCreate.
Catch the error in the thread, handle it there and then let the thread continue the work. To handle the error you could simply queue a method to the main thread to report the error, for example. I hope you aren't letting exceptions leave your thread Execute method.
This is the way that I implement it:
procedure TAPIRequest.DoRequest;
begin
FBusy := True;
Resume;
end;
procedure TAPIRequest.Execute;
begin
inherited;
while not Terminated do begin
HttpError := False;
try
Response := HTTP.Post(URL, Params);
ParseResponse;
except
HttpError := True;
end;
if Assigned(OnResponse) then
OnResponse();
FBusy := False;
Suspend;
end;
end;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
var
Form1: TForm1;
MyThread: TMyThread;
Event: TEvent;
procedure TForm1.FormCreate(Sender: TObject);
begin
Event := TEvent.Create(nil,true,false, '');
MyThread := TMyThread.Create(False);
end;
procedure TMyThread.Execute;
begin
while True do
begin
Event.WaitFor(Infinite);
// do something
Event.ResetEvent;
end;
end;
procedure RestartThread;
begin
Event.SetEvent;
// if you need check thread status, wait or run, use here
// if Event.WaitFor(0) = ...(wrTimeout, wrSignaled)
end;
TThread's resume method is deprecated in D2010. So, I thought it should now work like this:
TMyThread = class (TThread)
protected
Execute; override;
public
constructor Create;
end;
...
TMyThread.Create;
begin
inherited Create (True);
...
Start;
end;
Unfortunately I get an exception "Cannot call start on a running or supsended thread"...which seems weird to me considering the fact that the documentation tells me that I should call Start on a thread created in suspended mode.
What am I missing here?
The reason is that a Thread is not supposed to start itself.
The thread never knows when initialization is complete. Construction is not the same as initialization (construction should always be short and exception free; further initialization is done after construction).
A similar situation is a TDataSet: no TDataSet constructor should ever call Open, or set Active := True.
See also this blog entry by Wings of Wind.
You should either:
Create the TMyThread suspended by calling Create(true) and perform the Start outside your TMyThread class
Create the TMyThread non-suspeneded, making sure the Create constructor does full initialization, and let TThread.AfterConstruction start the thread.
Explanation of TThread usage:
Basically, a thread should be just that: the encapsulation of the context on which code is executed.
The actual code (the business logic) that is executed should then be in other classes.
By decoupling those two, you gain a lot of flexibility, especially initiating your business logic from within multiple places (which is very convenient when writing unit tests!).
This is the kind of framework you could use for that:
unit DecoupledThreadUnit;
interface
uses
Classes;
type
TDecoupledThread = class(TThread)
strict protected
//1 called in the context of the thread
procedure DoExecute; virtual;
//1 Called in the context of the creating thread (before context of the new thread actualy lives)
procedure DoSetUp; virtual;
//1 called in the context of the thread right after OnTerminate, but before the thread actually dies
procedure DoTearDown; virtual;
protected
procedure DoTerminate; override;
procedure Execute; override;
public
constructor Create;
procedure AfterConstruction; override;
end;
implementation
constructor TDecoupledThread.Create;
begin
// create suspended, so that AfterConstruction can call DoSetup();
inherited Create(True);
end;
procedure TDecoupledThread.AfterConstruction;
begin
// DoSetUp() needs to be called without the new thread in suspended state
DoSetUp();
// this will unsuspend the underlying thread
inherited AfterConstruction;
end;
procedure TDecoupledThread.DoExecute;
begin
end;
procedure TDecoupledThread.DoSetUp;
begin
end;
procedure TDecoupledThread.DoTearDown;
begin
end;
procedure TDecoupledThread.DoTerminate;
begin
inherited DoTerminate();
// call DoTearDown on in the thread context right before it dies:
DoTearDown();
end;
procedure TDecoupledThread.Execute;
begin
// call DoExecute on in the thread context
DoExecute();
end;
end.
You could even make it event based by something like this:
unit EventedThreadUnit;
interface
uses
Classes,
DecoupledThreadUnit;
type
TCustomEventedThread = class(TDecoupledThread)
private
FOnExecute: TNotifyEvent;
FOnSetUp: TNotifyEvent;
FOnTearDown: TNotifyEvent;
strict protected
procedure DoExecute; override;
procedure DoSetUp; override;
procedure DoTearDown; override;
public
property OnExecute: TNotifyEvent read FOnExecute write FOnExecute;
property OnSetUp: TNotifyEvent read FOnSetUp write FOnSetUp;
property OnTearDown: TNotifyEvent read FOnTearDown write FOnTearDown;
end;
// in case you want to use RTTI
TEventedThread = class(TCustomEventedThread)
published
property OnExecute;
property OnSetUp;
property OnTearDown;
end;
implementation
{ TCustomEventedThread }
procedure TCustomEventedThread.DoExecute;
var
TheOnExecute: TNotifyEvent;
begin
inherited;
TheOnExecute := OnExecute;
if Assigned(TheOnExecute) then
TheOnExecute(Self);
end;
procedure TCustomEventedThread.DoSetUp;
var
TheOnSetUp: TNotifyEvent;
begin
inherited;
TheOnSetUp := OnSetUp;
if Assigned(TheOnSetUp) then
TheOnSetUp(Self);
end;
procedure TCustomEventedThread.DoTearDown;
var
TheOnTearDown: TNotifyEvent;
begin
inherited;
TheOnTearDown := OnTearDown;
if Assigned(TheOnTearDown) then
TheOnTearDown(Self);
end;
end.
Or adapt it for DUnit TTestCase descendants like this:
unit TestCaseThreadUnit;
interface
uses
DecoupledThreadUnit,
TestFramework;
type
TTestCaseRanEvent = procedure (Sender: TObject; const TestResult: TTestResult) of object;
TTestCaseThread = class(TDecoupledThread)
strict private
FTestCase: TTestCase;
strict protected
procedure DoTestCaseRan(const TestResult: TTestResult); virtual;
function GetTestCase: TTestCase; virtual;
procedure SetTestCase(const Value: TTestCase); virtual;
protected
procedure DoExecute; override;
procedure DoSetUp; override;
procedure DoTearDown; override;
public
constructor Create(const TestCase: TTestCase);
property TestCase: TTestCase read GetTestCase write SetTestCase;
end;
implementation
constructor TTestCaseThread.Create(const TestCase: TTestCase);
begin
inherited Create();
Self.TestCase := TestCase;
end;
procedure TTestCaseThread.DoExecute;
var
TestResult: TTestResult;
begin
if Assigned(TestCase) then
begin
// this will call SetUp and TearDown on the TestCase
TestResult := TestCase.Run();
try
DoTestCaseRan(TestResult);
finally
TestResult.Free;
end;
end
else
inherited DoExecute();
end;
procedure TTestCaseThread.DoTestCaseRan(const TestResult: TTestResult);
begin
end;
function TTestCaseThread.GetTestCase: TTestCase;
begin
Result := FTestCase;
end;
procedure TTestCaseThread.SetTestCase(const Value: TTestCase);
begin
FTestCase := Value;
end;
procedure TTestCaseThread.DoSetUp;
begin
if not Assigned(TestCase) then
inherited DoSetUp();
end;
procedure TTestCaseThread.DoTearDown;
begin
if not Assigned(TestCase) then
inherited DoTearDown();
end;
end.
--jeroen
Short answer: call inherited Create(false) and omitt the Start!
The actual Start of a non-create-suspended thread is done in AfterConstruction, which is called after all constructors have been called.