Access to vcl component in thread! Delphi - multithreading

So, my goal is start a function in another thread. Also i need access to other vcl components from new thread. Here is my code so far:
procedure TForm1.StartButtonClick(Sender: TObject);
var
thread1: integer;
id1: longword;
begin
thread1 := beginthread(nil,0,Addr(Tform1.fetchingdata),nil,0,id1);
closehandle(thread1);
end;
procedure TForm1.FetchingData;
var
...
begin
Idhttp1.IOHandler := IdSSLIOHandlerSocketOpenSSL1; //<- error
idhttp1.Request.ContentType := 'application/x-www-form-urlencoded';
my program hangs and i get error: Exception EAccessViolation in module my.exe at 00154E53. Access violation at address 00554E53 in module 'my.exe'. Read of address 00000398.
Thanks in advance.

The cause of the AV is that you pass the address of a TForm method to a function that expects a TThreadFunc (see the documentation of System.BeginThread()). Using Addr() like this is a good way to keep the compiler from pointing out your bugs.
What you would need to do instead is to write a wrapper function that has the correct signature, pass the form instance as the parameter, and call the method on the form from that function.
But don't go there, either write your code as a descendant of TThread, or (preferably) use a higher level wrapper like AsyncCalls or the Omni Thread Library. And make sure that you don't access VCL components in the main thread, create and free those that you need in your worker thread.

The VCL (Gui components) is only to be accessed from the main thread. Other threads need the main thread to access the VCL.

You could try the same thing with a regular TThread if you're using Delphi or Lazarus.
type
TSeparateThread = class(TThread)
private
protected
public
constructor Create(IfSuspend: Boolean);
proceedure Execute; override;
// variables to fill go here
// s : String;
// i : Integer;
// etc...
end;
constructor TSeparateThread.Create(IfSuspend: Boolean);
begin
inherited Create(IfSuspend);
end;
procedure TSeparateThread.Execute;
begin
// This is where you will do things with those variables and then pass them back.
YourMainUnitOrForm.PublicVariableOf := s[i];
// passes position 0 of s to PublicVariableOf in your Main Thread
end;
Calling the new Thread is done as follows:
with TSeparateThread.Create(true) do
begin
// This is where you fill those variables passed to the new Thread
s := 'from main program';
i := 0;
// etc...
Resume;
//Will Start the Execution of the New Thread with the variables filled.
end;

Related

Delphi: multithread help for beginner [duplicate]

I am new with this stuff of Threading in Delphi. so, I am trying to make a simple query aplication that make a bit call up for the database and take a bit of time, so I want to alert the user that there is a background process and have to be patient.
I tried many samples, but none of them work for me, Please, could somebody show me a simple sample that could work?
I know that I have to Declare a Type of TThread, with Create and Override Execute... etc.. but since that I am lost...
Using Delphi 7, SQL Server 2005 and ADO, Windows XP sp3.-
Thanks.
Yup, you declare a new type which inherits from TThread:
TMyWorkerThread = class(TThread)
end;
Then you add a function override for Execute():
TMyWorkerThread = class(TThread)
public
procedure Execute; override;
end;
That procedure will be called when you start your thread. It will be executed in parallel with your main program. Let's write it.
procedure TMyWorkerThread.Execute;
begin
//Here we do work
DoSomeWork();
DoMoreWork();
//When we exit the procedure, the thread ends.
//So we don't exit until we're done.
end;
How to use this? Let's say you want to start doing work when the user clicks button. You write an OnClick handler:
procedure TMainForm.Button1Click(Sender: TObject);
begin
TMyWorkerThread.Create(false);
end;
That's it. After the user clicks button, your thread starts and proceeds with doing whatever it is that you wrote in Execute. If the user clicks the button again, another thread will start, and then another - one every click. They will all run in parallel, each doing all what's written in Execute() and then ending.
Let's say you want to check if the work is over. For that, you'll have to store the reference to your thread somewhere:
TMainForm = class(TForm)
{...skipped...}
public
MyWorkerThread: TThread;
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
//This time we make sure only one thread can be started.
//If one thread have been started already, we don't start another.
if MyWorkerThread<>nil then
raise Exception.Create('One thread have already been started!');
MyWorkerThread := TMyWorkerThread.Create(false);
end;
procedure TMainForm.Button2Click(Sender: TObject);
begin
//If the work is not over yet, we display message informing the user we're still working
if (MyWorkerThread<>nil) and (WaitForSingleObject(MyWorkerThread.Handle, 0)<>WAIT_OBJECT_0) then
MessageBox(Self.Handle, pchar("The work is not yet done!"), pchar("Still running"), MB_OK);
end;
As you see, we're checking if a thread is still running by calling a Windows function called WaitForSingleObject. This function waits until the thread is done working, or the timeout is elapsed, and as we specify the timeout of 0, it just exists immediately if the thread is not over yet.
You can find many examples on the web of threads. The only special feature, if you are using ADO connections inside the Thread, is that you can't share the same connection.
Each thread must create its own connection, otherwise they are equal (should follow the same rules as any other thread.)
An sample that I have used is this:
TADOSQLThread = class(TThread)
private
FADOQ: TADOQuery; // Internal query
FSQL: string; // SQL To execute
FID: integer; // Internal ID
public
constructor Create(CreateSuspended:Boolean; AConnString:String;
ASQL:string; IDThread:integer);
destructor Destroy; override;
procedure Execute(); override;
property ID:integer read FID write FID;
property SQL:string read FSQL write FSQL;
property ADOQ:TADOQuery read FADOQ write FADOQ;
end;
The Create constructor is overrided, and look like this:
constructor TADOSQLThread.Create(CreateSuspended:Boolean; AConnString:String;
ASQL:string; IDThread:integer);
begin
inherited Create(CreateSuspended);
// ini
Self.FreeOnTerminate := False;
// Create the Query
FADOQ := TAdoquery.Create(nil);
// assign connections
FADOQ.ConnectionString := AConnString;
FADOQ.SQL.Add(ASQL);
Self.FID := IDThread;
Self.FSQL:= ASQL;
end;
And the execute method is very simple:
procedure TADOSQLThread.Execute();
begin
inherited;
try
// Ejecutar la consulta
Self.FADOQ.Open;
except
// Error al ejecutar
...Error treattement
end;
end;
To start and create a thread you can use code similar to this:
//crear el Thread
th := TADOSQLThread.Create(True, mmConnection.Lines.Text, ASQL, AId);
// internal for me (for controled the number of active threads and limete it)
inc(numThreads);
// evento finalizacion
th.OnTerminate := TerminateThread;
// Ejecutarlo
th.Resume;
I have create a TerminateThread method that receive the control of threads when they finish. The only different to other threads is the connection problem. You must create a new connection on every thread, It can't share the same ADOConnections with others.
I hope this example will be useful for you.
Regards

main application locks up using idPop3 to retrieve mail messages (even in a thread)

I am using a thread to access a pop3 account and retrieve messages. It works fine, but it locks up my application until it is complete. Cant move the window, shut down, click buttons, nothing.
It runs fine and allows me to access the main application up until the spot i commented out (or after the IdPOP31.Connect();)
//Getting the number of the messages that server has
then it locks up
procedure TPopThread.Pop;
var
vName, vEmail, vServerIn, vServerOut, vUserId, vPassword: String;
vPop3Port, vSMTPPort, vSSL: String; vHTML: TStringList;
MsgCount : Integer;
i,j : Integer;
FMailMessage : TIdMessage;
begin
with frmMain do
begin
RzMemo1.Lines.Clear;
vHTML:= TStringList.Create;
GetAccount(lbxMain.SelectedItem,vName, vEmail, vServerIn, vServerOut, vUserId, vPassword,
vPop3Port, vSMTPPort, vSSL, vHTML);
IdPOP31.Host := vServerIn;
IdPOP31.Username := vUserId;
IdPOP31.Password := vPassword;
IdPOP31.Port := StrToInt(vPop3Port);
try
Prepare(IdPOP31);
IdPOP31.Connect();
// {
// //Getting the number of the messages that server has.
// MsgCount := IdPOP31.CheckMessages;
// for i:= 0 to Pred(MsgCount) do
// begin
// try
// FMailMessage := TIdMessage.Create(nil);
// IdPOP31.Retrieve(i,FMailMessage);
// RzMemo1.Lines.Add('=================================================');
// RzMemo1.Lines.Add(FMailMessage.From.Address);
// RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
// RzMemo1.Lines.Add(FMailMessage.Subject);
// RzMemo1.Lines.Add(FMailMessage.Sender.Address);
// RzMemo1.Lines.Add(FMailMessage.Body.Text);
//
// for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
// begin
// // if the part is an attachment
// if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
// begin
// RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
// end;
// end;
// RzMemo1.Lines.Add('=================================================');
// finally
// FMailMessage.Free;
// end;
// RzMemo1.Clear;
// end;
// }
finally
IdPOP31.Disconnect;
vHTML.Free;
end;
end;
end;
It actually did this before I added the thread, so it has something to do with that portion that is commented out and not the thread
What did i do wrong or didn't do?
here is my Execute
procedure TPopThread.Execute;
begin
try
Synchronize(Pop);
except
on Ex: Exception do
fExceptionMessage := Ex.Message;
end;
end;
here is how i call it
PopThread := TPopThread.Create(lbxMain.SelectedItem, frmMain.DonePopping);
You are locking up the application yourself, because you're synchronizing the call to the pop method.
Synchronize causes the call specified by AMethod to be executed using the main thread, thereby avoiding multithread conflicts.
The current thread is passed in the AThread parameter.
If you are unsure whether a method call is thread-safe, call it from within the Synchronize method to ensure it executes in the main thread.
Execution of the current thread is suspended while the method executes in the main thread.
So, for practical purposes, you're like you have no extra thread, since all your code is executed in the main thread.
An example of when you would want to use Synchronize is when you want to interact with a VCL component
On the other hand, because you're directly accessing a number of visual controls from your method, and the VCL is not thread safe, you have to execute your method in the main thread.
The best you can do is to make your thread independent from the VCL by not accessing any VCL component from the thread, but rather collecting all the input and output values in memory and setting/reading it from the main thread before the thread starts and after the thread finishes.
Or, if for any reason you don't want to do that, you can dissect your method to separate the parts that need access to the VCL and synchronize only that parts, for example:
type
TPopThread = class
private
FMailMessage : TIdMessage; //now the message belongs to the class itself
...
public
//all the values are passed via constructor or the thread is
//created in suspended state, configured and then started
property Host: string read FHost write FHost;
property UserName: string read FUserName write FUserName;
property Password: string read ...;
property Port: Integer read ...;
end;
procedure TPopThread.CopyMailToGUI;
var
J: Integer;
begin
frmMain.RzMemo1.Lines.Add('=================================================');
frmMain.RzMemo1.Lines.Add(FMailMessage.From.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
frmMain.RzMemo1.Lines.Add(FMailMessage.Subject);
frmMain.RzMemo1.Lines.Add(FMailMessage.Sender.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Body.Text);
for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
begin
// if the part is an attachment
if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
begin
frmMain.RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
end;
end;
frmMain.RzMemo1.Lines.Add('=================================================');
end;
procedure TPopThread.Pop;
var
MsgCount : Integer;
i,j : Integer;
Pop: TIdPOP3;
begin
Pop := TIdPOP3.Create(nil);
try
Pop.Host := FHost;
Pop.Username := FUserName;
Pop.Password := FPassword;
Pop.Port := FPort;
Prepare(Pop);
Pop.Connect();
//Getting the number of the messages that server has.
MsgCount := Pop.CheckMessages;
for I := 0 to Pred(MsgCount) do
begin
try
FMailMessage := TIdMessage.Create(nil);
try
IdPOP31.Retrieve(i,FMailMessage);
Synchronize(CopyMailToGUI);
finally
FMailMessage.Free;
end;
end;
finally
Pop.Free;
end;
end;
procedure TPopThread.Execute;
begin
//no need of a try/except, if an exception occurs, it
//is stored in the FatalException property
Pop;
end;
Now, your thread will ask the main thread to copy just the processed message to the VCL. During that copy your thread will block and your application will not respond to messages because the main thread is busy, but that will be for very shorts intervals, so even if it is not the ideal case, I think it will work for what you want.
You put all your logic inside a Synchronize call. Synchronize runs its function in the main VCL thread, so you've essentially nullified any benefits you might have gained from using a separate thread in the first place.
Remove the call to Synchronize so that Pop runs in the thread you created for it.
If you still need some operations to execute in the main thread, then put them in subroutines so that you can run only them in Synchronize. The parts I see in that code are the places where you add lines to a memo control.

Create event and share variables

I am using Delphi 2007 and threads.
My problem (sorry, i'll try to explain better):
1) I created a file "utilities.pas" where i have the function i use more.
2) I created a new program, in this program i have one thread
3) in the execute method of the thread i call one function in my file "utilities.pas".
this function connects to an ftp using clever components (tclftp). This components logs the server responce in a dedicated event. What i would like to do is to save the log in a stringlist and then send the stringlist back to the calling thread.
This is part of the file "utilities.pas":
// I created TEventHandlers because it's the only way to assign the event runtime
// without having a class
type
TEventHandlers = class
procedure clFtp1SendCommand(Sender: TObject; const AText: string);
end;
var EvHandler: TEventHandlers;
// this is the porcedure called from the thread. i want to send the stringlist
// back to it containing the ftp log
procedure Test(VAR slMain: tStringlist);
var cFTP: TclFtp;
begin
cFTP := TclFtp.Create(nil);
cFTP.Server := 'XXX';
cFTP.UserName := 'XXX';
cFTP.Password := 'XXX';
cFTP.OnSendCommand := EvHandler.clFtp1SendCommand;
// i connect to the ftp
cFTP.Open;
FreeAndNil(cFTP);
end;
procedure TEventHandlers.clFtp1SendCommand(Sender: TObject; const AText: string);
begin
// here the component (cftp) sends me back the answer from the server.
// i am logging it
// HERE IT'S THE PROBLEM:
// I can't reach slMain from here.....
slmain.add(Atext);
end;
this is the calling thread:
procedure TCalcThread.Execute;
var slMain: tstringlist;
begin
inherited;
slmain := tstringlist.create(nil);
Test(slmain);
if slMain.count > 0 then
slMain.savetofile('c:\a.txt');
// i won't free the list box now, but in the thread terminated.
end;
this is the main program:
procedure TfMain.ThreadTerminated(Sender: TObject);
Var ExThread: TCalcThread;
begin
ExThread := (Sender as TCalcThread);
if ExThread.slMain.Count > 0 then
ExThread.slMain.SaveToFile('LOG\Errori.log');
freeandnil(slMain);
end;
Please can anybody help me in solving this? I really don't know what to do.
I hope now it more clear.
p.s. thanks for all the answer..
Another approach would be to have your thread object have its own instance of the stringlist and its own cFTP. If you need to have one "master thread" that everything writes to (perhaps for a summary of what each thread accomplished), use this class:
TThreadStringList by Tilo Eckert
http://www.swissdelphicenter.ch/torry/showcode.php?id=2167
I think one (BAD) approach would be to create a pool of components in the main thread or at design time, and assign one to each thread. i.e. 5 instances of cFTP, 5 stringlists, 5 threads.
Update: Martin James points out why this is a terrible idea, and I agree. So don't do this. Post stays as a deterrent.
Intercept the event within the thread class, and fire an own typed event from within that handler. Synchronize this call! And try to prevent the global variable. All this as follows:
type
TFtpSendCommandEvent = procedure(Mail: TStrings; const AText: String) of object;
TMyThread = class(TThread)
private
FclFtp: TclFtp;
FslMail: TStrings;
FOnFtpSendCommand: TFtpSendCommandEvent;
FText: String;
procedure clFtpSendCommand(Sender: TObject; const AText: String);
procedure DoFtpSendCommand;
protected
procedure Execute; override;
public
// You could add this property as parameter to the constructor to prevent the
// need to assign it separately
property OnFtpSendCommand: TFtpSendCommandEvent read FOnFtpSendCommand
write FOnFtpSendCommand;
end;
// If you dont want to make this a property or private field of the thread class:
var
EvHandler: TFtpSendCommandEvent;
{ TMyThread }
procedure TMyThread.clFtpSendCommand(Sender: TObject; const AText: string);
begin
// Store the AText parameter temporarily in a private field: Synchronize only
// takes a parameterless method
FText := AText;
Synchronize(DoFtpSendCommand);
end;
procedure TMyThread.DoFtpSendCommand;
begin
if Assigned(FOnFtpSendCommand) then
FOnFtpSendCommand(FslMail, FText);
// Or, if you really like to use that global variable:
if Assigned(EvHandler) then
EvHandler(FslMail, FText);
end;
procedure TMyThread.Execute;
begin
...
FclFtp := TclFtp.Create(nil);
FslMail := TStringList.Create(nil);
try
FclFtp.Server := 'XXX';
FclFtp.UserName := 'XXX';
FclFtp.Password := 'XXX';
FclFtp.OnSendCommand := clFtpSendCommand;
FclFtp.Open;
finally
FreeAndNil(FclFtp);
FreeAndNil(FslMail);
end;
...
end;

DLL, Form and Thread (all in one) problem in delphi

there is a very complex application which i try to build.
There is a DLL library which i create. I put a form in it and i put a Thread in it.
in DLL i have a function:
procedure ShowForm; stdcall;
var
Form1 : TFormSNVFL7;
begin
Form1 := TFormSNVFL7.Create(nil);
Form1.Show;
end;
i create a form and show it. there isn't problem in here.
I add a thread to this dll.
i put a timer on the form. after a couple of seconds i create a thread and run it. everything is going normal but when i try to change anything of the form, nothing happen.
in synchronize function i try to change a label on it but nothing happen.
Here is the files:
DLL pas:
library uploader;
uses
SysUtils,
Classes,
Forms,
UploaderForm in 'UploaderForm.pas' {FormUploader},
ThreadUpload in 'ThreadUpload.pas';
{$R *.res}
procedure ShowForm; stdcall;
var
upForm: TFormUploader;
begin
upForm := TFormUploader.Create(nil);
upForm.Show;
end;
exports
ShowForm;
begin
end.
Form pas:
unit UploaderForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, acPNG, ExtCtrls, JvExExtCtrls, JvImage, JvExControls, JvLabel,
JvAnimatedImage, JvGIFCtrl, ComCtrls, JvExComCtrls, JvProgressBar, StdCtrls,
FileCtrl, JvDriveCtrls;
type
TFormUploader = class(TForm)
imgRunning: TJvImage;
imgReady: TJvImage;
imgUpdate: TJvImage;
JvLabel1: TJvLabel;
JvLabel2: TJvLabel;
imgConnect: TJvImage;
imgUpload: TJvImage;
imgCheck: TJvImage;
JvLabel3: TJvLabel;
JvLabel4: TJvLabel;
JvLabel5: TJvLabel;
JvLabel6: TJvLabel;
imgRun: TJvImage;
imgOK: TJvImage;
imgDone: TJvImage;
JvProgressBar1: TJvProgressBar;
JvLabel7: TJvLabel;
fileList: TJvFileListBox;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormUploader: TFormUploader;
implementation
{$R *.dfm}
Uses ThreadUpload;
procedure TFormUploader.FormCreate(Sender: TObject);
begin
imgUpdate.Picture := imgReady.Picture;
imgConnect.Picture := imgReady.Picture;
imgUpload.Picture := imgReady.Picture;
imgCheck.Picture := imgReady.Picture;
imgRun.Picture := imgReady.Picture;
imgOK.Picture := imgReady.Picture;
fileList.Directory := ExtractFilePath(Application.ExeName) + 'csvexport/';
end;
procedure TFormUploader.Timer1Timer(Sender: TObject);
var
UpThread: TThread;
begin
Timer1.Enabled := False;
UpThread := UploadThread.Create(true);
UpThread.Create;
UpThread.Resume;
end;
end.
Thread pas:
unit ThreadUpload;
interface
uses
Classes, UploaderForm;
type
UploadThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ UploadThread }
procedure UploadThread.Execute;
begin
With FormUploader do
begin
imgUpdate.Picture := imgRunning.Picture;
end;
end;
end.
i cannot solve this problem.
TThread.Synchronize() does not work in a DLL by default, as the sync queue that Synchronize() posts to is local to the executable that is calling it. In other words, when Synchronize() is called by the application, it posts to a queue that is local to the exe file. When Synchronize() is called by a DLL, it posts to a queue that is local to the dll file. When the application pumps its sync queue during idle times, it will not pump the DLL's queue automatically. You have to export a function from your DLL that your application can then call when needed, such as in the TApplication.OnIdle event or in a timer. That exported function can then call the RTL's CheckSynchronize() function to pump the DLL's sync queue.
Simple
You are changing a property from the FormUploader var from the unit UploaderForm in the UpThread
But in the unit DLL.pas you are creating other object from TFormUploader
Try to do this in the procedure that show the form:
procedure ShowForm; stdcall;
begin
FormUploader := TFormUploader.Create(nil);
FormUploader.Show;
end;
Do this, and the problem are solved
Your problem results from using VCL and threading. You should never call VCL related code from a thread without using synchronization mechanisms.
Normally you create a VCL application by using TApplication and TApplication.Run() to create a main loop of your program. The main loop handles windows messages and other stuff, but also calls CheckSynchronize() whereas CheckSynchronize() looks up whether there is call queued that should be synchronized (that is a call that is added to the queue by using TThread.Synchronize()). So when you create a thread i runs concurrently to the main loop and that's where your problem begins.
You should either move the picture assignment code to a separate method in TFormUploader and call that method by using TThread.Synchronize() or use other synchronization mechanisms like event objects (TEvent / CreateEvent()).
I had a similar issue trying to update a TToolButton icon in the main EXE, from a callback function invoked by the DLL. The DLL invokes the callback function in response to a broadcast to channel message sent via a DataSnap implementation, I think in a child thread.
Accessing the TToolButton directly from the EXE callback function results in flickering the TToolBar and disappearing the icons.
I created a TThread object and the interaction with the TToolButton is managed using the TThread.Synchonize() function into the main thread: this solved the problem for me.
interface
type
TCallBackThread=class(TThread)
private
procedure DoInSync;
public
procedure Execute; override;
end;
var
CallBackThread: TCallBackThread;
implementation
procedure TCallBackThread.DoInSync;
begin
// Jobs to be done in main thread
end;
procedure TCallBackThread.Execute;
begin
inherited;
Synchronize(DoInSync);
end;
The callback function into the EXE is:
procedure ConnectWf_Callback(s: PAnsiChar); stdcall;
begin
if not Assigned(CallBackThread) then begin
CallBackThread := TCallBackThread.Create(true);
CallBackThread.Resume;
end else begin
CallBackThread.Execute;
end;
end;

Simple Thread Sample Delphi

I am new with this stuff of Threading in Delphi. so, I am trying to make a simple query aplication that make a bit call up for the database and take a bit of time, so I want to alert the user that there is a background process and have to be patient.
I tried many samples, but none of them work for me, Please, could somebody show me a simple sample that could work?
I know that I have to Declare a Type of TThread, with Create and Override Execute... etc.. but since that I am lost...
Using Delphi 7, SQL Server 2005 and ADO, Windows XP sp3.-
Thanks.
Yup, you declare a new type which inherits from TThread:
TMyWorkerThread = class(TThread)
end;
Then you add a function override for Execute():
TMyWorkerThread = class(TThread)
public
procedure Execute; override;
end;
That procedure will be called when you start your thread. It will be executed in parallel with your main program. Let's write it.
procedure TMyWorkerThread.Execute;
begin
//Here we do work
DoSomeWork();
DoMoreWork();
//When we exit the procedure, the thread ends.
//So we don't exit until we're done.
end;
How to use this? Let's say you want to start doing work when the user clicks button. You write an OnClick handler:
procedure TMainForm.Button1Click(Sender: TObject);
begin
TMyWorkerThread.Create(false);
end;
That's it. After the user clicks button, your thread starts and proceeds with doing whatever it is that you wrote in Execute. If the user clicks the button again, another thread will start, and then another - one every click. They will all run in parallel, each doing all what's written in Execute() and then ending.
Let's say you want to check if the work is over. For that, you'll have to store the reference to your thread somewhere:
TMainForm = class(TForm)
{...skipped...}
public
MyWorkerThread: TThread;
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
//This time we make sure only one thread can be started.
//If one thread have been started already, we don't start another.
if MyWorkerThread<>nil then
raise Exception.Create('One thread have already been started!');
MyWorkerThread := TMyWorkerThread.Create(false);
end;
procedure TMainForm.Button2Click(Sender: TObject);
begin
//If the work is not over yet, we display message informing the user we're still working
if (MyWorkerThread<>nil) and (WaitForSingleObject(MyWorkerThread.Handle, 0)<>WAIT_OBJECT_0) then
MessageBox(Self.Handle, pchar("The work is not yet done!"), pchar("Still running"), MB_OK);
end;
As you see, we're checking if a thread is still running by calling a Windows function called WaitForSingleObject. This function waits until the thread is done working, or the timeout is elapsed, and as we specify the timeout of 0, it just exists immediately if the thread is not over yet.
You can find many examples on the web of threads. The only special feature, if you are using ADO connections inside the Thread, is that you can't share the same connection.
Each thread must create its own connection, otherwise they are equal (should follow the same rules as any other thread.)
An sample that I have used is this:
TADOSQLThread = class(TThread)
private
FADOQ: TADOQuery; // Internal query
FSQL: string; // SQL To execute
FID: integer; // Internal ID
public
constructor Create(CreateSuspended:Boolean; AConnString:String;
ASQL:string; IDThread:integer);
destructor Destroy; override;
procedure Execute(); override;
property ID:integer read FID write FID;
property SQL:string read FSQL write FSQL;
property ADOQ:TADOQuery read FADOQ write FADOQ;
end;
The Create constructor is overrided, and look like this:
constructor TADOSQLThread.Create(CreateSuspended:Boolean; AConnString:String;
ASQL:string; IDThread:integer);
begin
inherited Create(CreateSuspended);
// ini
Self.FreeOnTerminate := False;
// Create the Query
FADOQ := TAdoquery.Create(nil);
// assign connections
FADOQ.ConnectionString := AConnString;
FADOQ.SQL.Add(ASQL);
Self.FID := IDThread;
Self.FSQL:= ASQL;
end;
And the execute method is very simple:
procedure TADOSQLThread.Execute();
begin
inherited;
try
// Ejecutar la consulta
Self.FADOQ.Open;
except
// Error al ejecutar
...Error treattement
end;
end;
To start and create a thread you can use code similar to this:
//crear el Thread
th := TADOSQLThread.Create(True, mmConnection.Lines.Text, ASQL, AId);
// internal for me (for controled the number of active threads and limete it)
inc(numThreads);
// evento finalizacion
th.OnTerminate := TerminateThread;
// Ejecutarlo
th.Resume;
I have create a TerminateThread method that receive the control of threads when they finish. The only different to other threads is the connection problem. You must create a new connection on every thread, It can't share the same ADOConnections with others.
I hope this example will be useful for you.
Regards

Resources