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.
Related
I'm beginner in Delphi programing.
I use a thread to communicate with my server and I want to pass a Tevent to my thread at creation time and use it to signal a task on this thread from main thread and finally on thread clear the event. This event set in main thread to signal the task on my net thread and finally clear after task completed in the net thread.
I use this line to create thread on run time. All work fine but after adding event to my code rise a problem.
Net_thread:= TNetThread.Create(user, password, TheCallback, Tevent);
TNetThread is my thread class on other unit and Net_thread is my net thread.
TheCallback is a procedure for change UI from thread. Declare this type in TNetThread.
user, password are login data collected in GUI.
Tevent is a handle to my event created in main thread and pass it to Net_thread.
Before I add event to my code I only pass 2 string and a procedure to thread and I have no problem. ...Create(user, password, TheCallback); after add event to my code and pass it as THandle to my thread can not use it. Its like a Cardinal variable and when I try to check its state with this code:
System.SyncObjs.TEvent.WaitFor(FEvent)
I have an error e2076. FEvent set on constructor TNetThread.Create and equal to Tevent received from main thread.
Please give me a simple example?
This is my minimal code:
on main form:
procedure TMainform.FormCreate(Sender: TObject);
var
T_event: THandle;
begin
T_event: := CreateEvent(nil, True, False, nil);
Net_thread:= TNetThread.Create(user, password, TheCallback, T_event);
end;
procedure TMainform.TheCallback(const st,h : String);
begin
//recive data from net thread
end;
on event in main thread
SetEvent(T_event);
And on other unit
type
TMyCallback = procedure(const st, h : String) of object;
TNetThread = class(TThread)
IdTCPClient1: TIdTCPClient;
private
FCallback : TMyCallback;
FEvent: THandle;
protected
procedure execute; override;
procedure SendLog(st, h: string);
public
constructor Create(user_n, psw: string ;aCallback : TMyCallback ; const AEvent: THandle);
end;
constructor TNetThread.Create(user_n, psw: string ;aCallback: TMyCallback; const AEvent: THandle);
begin
inherited Create(false);
FCallback := aCallback;
FEvent := AEvent;
user_name := user_n;
password:= psw;
FreeOnTerminate := true;
end;
procedure TNetThread.SendLog(st ,h: string);
begin
if not Assigned(FCallback) then
Exit;
Self.Queue( // Executed later in the main thread
procedure
begin
FCallback(st, h);
end
);
end;
procedure TNetThread.Execute;
begin
.
.
if (System.SyncObjs.TEvent.WaitFor(FEvent) = wrSignaled) then...
.
.
end;
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?
I am implementing a pool of objects in Delphi. I need to synchronize the threads to get the objects from the pool.
Thread Code:
uClientQueryPool.CLIENT_POOL_GUARD.Acquire();
QueryClient := QUERY_POOL.GetClient();
uClientQueryPool.CLIENT_POOL_GUARD.Release;
Pool Code:
var
CLIENT_POOL_GUARD: TCriticalSection;
type
TClientQueryPool = class
public
function GetClient(): TQueryClient;
end;
The CLIENT_POOL_GUARD is a unit variable. The pool is working well, but can I use "uClientQueryPool.CLIENT_POOL_GUARD.Acquire();" and "uClientQueryPool.CLIENT_POOL_GUARD.Release;" inside the GetClient method?
Like this:
function TClientQueryPool.GetClient: TQueryClient;
begin
CLIENT_POOL_GUARD.Acquire();
...
CLIENT_POOL_GUARD.Release;
end;
Moving the lock inside the get/pop/whatever method is just fine, as is making the CriticalSection instance a private member of the pool class. Use the same CS in the release() call that pushes the objects back onto the pool.
Been doing this for decades, usually with TObjectQueue as the pool queue, a CS to protect it and a semaphore to count the pool contents and something for requesting threads to block on if the pool empties temporarily.
Don't know where that 'double acquire' thread came from. Either the lock is inside the pool class, or outside. I really can't imagine why anyone would code up both!
Example classes:
First, thread-safe P-C queue, for holding the pooled objects:
unit tinySemaphoreQueue;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs;
type
pObject=^Tobject;
TsemaphoreMailbox=class(TobjectQueue)
private
countSema:Thandle;
protected
access:TcriticalSection;
public
property semaHandle:Thandle read countSema;
constructor create; virtual;
procedure push(aObject:Tobject); virtual;
function pop(pResObject:pObject;timeout:DWORD):boolean; virtual;
end;
implementation
{ TsemaphoreMailbox }
constructor TsemaphoreMailbox.create;
begin
inherited Create;
access:=TcriticalSection.create;
countSema:=createSemaphore(nil,0,maxInt,nil);
end;
function TsemaphoreMailbox.pop(pResObject: pObject;
timeout: DWORD): boolean;
begin // wait for a unit from the semaphore
result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
if result then // if a unit was supplied before the timeout,
begin
access.acquire;
try
pResObject^:=inherited pop; // get an object from the queue
finally
access.release;
end;
end;
end;
procedure TsemaphoreMailbox.push(aObject: Tobject);
begin
access.acquire;
try
inherited push(aObject); // shove the object onto the queue
finally
access.release;
end;
releaseSemaphore(countSema,1,nil); // release one unit to semaphore
end;
end.
then object pool:
unit tinyObjectPool;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs,
tinySemaphoreQueue;
type
TobjectPool=class;
TpooledObject=class(TObject)
private
FmyPool:TObjectPool;
protected
Fparameter:TObject;
public
procedure release;
constructor create(parameter:TObject); virtual;
end;
TpooledObjectClass=class of TpooledObject;
TobjectPool=class(TsemaphoreMailbox)
private
Fparameter:TObject;
function getPoolLevel: integer;
public
property poolLevel:integer read getPoolLevel;
constructor create(poolDepth:integer;
pooledObjectClass:TpooledObjectClass;parameter:TObject); reintroduce; virtual;
end;
implementation
{ TobjectPool }
constructor TobjectPool.create(poolDepth: integer;
pooledObjectClass: TpooledObjectClass;parameter:TObject);
var objectCount:integer;
thisObject:TpooledObject;
begin
inherited create;
Fparameter:=parameter; // a user parameter passed to all objects
for objectCount:=0 to poolDepth-1 do // fill up the pool with objects
begin
thisObject:=pooledObjectClass.create(parameter);
thisObject.FmyPool:=self;
inherited push(thisObject);
end;
end;
function TobjectPool.getPoolLevel: integer;
begin
access.acquire;
result:=inherited count;
access.release;
end;
{ TpooledObject }
constructor TpooledObject.create(parameter: TObject);
begin
inherited create;
Fparameter:=parameter;
end;
procedure TpooledObject.release;
begin
FmyPool.push(self);
end;
end.
Yes you can. Note, though that although you can pull an object from the pool in a thread-safe manner, it may not be thread-safe to use it if the object itself isn't thread-safe. For instance, in the example below, the pool is thread safe and even makes threads wait if all objects in the pool are in use, but once an object is in use, using it still is not thread safe, because it uses global data.
uses
SyncObjs;
var
GlobalData: Integer = 0;
type
TDataObject = class
Used: Boolean;
procedure UpdateData;
end;
type
TPool = class
FLock: TCriticalSection;
FSemaphore: TSemaphore;
FDataObjects: array[0..9] of TDataObject;
constructor Create;
destructor Destroy; override;
function GetDataObject: TDataObject;
procedure ReleaseDataObject(AObject: TDataObject);
end;
var
Pool: TPool;
type
TDataThread = class(TThread)
constructor Create;
procedure Execute; override;
end;
{ TPool }
constructor TPool.Create;
var
i: Integer;
begin
inherited Create;
FLock := TCriticalSection.Create;
FSemaphore := TSemaphore.Create(nil, Length(FDataObjects), Length(FDataObjects), '', False);
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i] := TDataObject.Create;
end;
destructor TPool.Destroy;
var
i: Integer;
begin
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i].Free;
FSemaphore.Free;
FLock.Free;
end;
function TPool.GetDataObject: TDataObject;
var
i: Integer;
begin
Result := nil;
FLock.Acquire;
try
FSemaphore.Acquire;
for i := Low(FDataObjects) to High(FDataObjects) do
if not FDataObjects[i].Used then
begin
Result := FDataObjects[i];
Result.Used := True;
Exit;
end;
Assert(Result <> nil, 'Pool did not return an object');
finally
FLock.Release;
end;
end;
procedure TPool.ReleaseDataObject(AObject: TDataObject);
begin
if not AObject.Used then
raise Exception.Create('Data object cannot be released, because it is not in use.');
AObject.Used := False;
FSemaphore.Release;
end;
{ TDataObject }
procedure TDataObject.UpdateData;
begin
Inc(GlobalData);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TDataThread.Create;
end;
{ TDataThread }
constructor TDataThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
Resume;
end;
procedure TDataThread.Execute;
var
DataObject: TDataObject;
begin
DataObject := Pool.GetDataObject;
DataObject.UpdateData; // <-- Not thread-safe!
Pool.ReleaseDataObject(DataObject);
end;
initialization
Pool := TPool.Create;
finalization
Pool.Free;
end.
1) I'd remove Acquire/Release code from threads code - it is fragile. In one thread you forget to call it - and ba-bang! Security measures, as a rule of thumb, should be centralized and enforced by server, not distributed in fuzzy way in clients.
2) Acquire/Release calls should be guarded from errors, else any stray exception would forever lock all the threads.
function TClientQueryPool.GetClient: TQueryClient;
begin
CS.Acquire;
try
// actually getting object, preferably just calling
// internal non-public thread-unsafe method for it
finally
CS.Release;
end;
end;
3) Critical section itself should better be a Pool's internal, non-public member. That way you would be allowed in future, when you forget of implementation details, easy refactoring, like:
3.1) implementing several pools
3.2) moving pool code to another unit
3.3) ensuring any stray erroneous code outside pool would not be able to crash the application be randomly acquiring or releasing the CS
4) Double calling of acquire/release over TCriticalSection object puts all your bets over implications from a single note in TCriticalSection documentation, pointed to by The_Fox.
"Each call to Release should be balance by an earlier call to Acquire"
http://docwiki.embarcadero.com/Libraries/en/System.SyncObjs.TCriticalSection.Release
And over the hope that all other Pascal implementations today and tomorrow would not miss it.
That is fragile practice. And multi-threading code is famous for creating Heisenbugs, when there are problems at clients sites, but you can not reproduce and find it in house.
If in future your company would expand to different platform or different language implementation, that puts a potential land mine. And the kind of mine, that would be hard to find by testing in house. Multithreading code is the place where you'd better be over-defeinsive and just do not allow ANY uncertainty to happen.
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;