37

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.

Jose M. Vilomar
  • 477
  • 1
  • 6
  • 13
  • 5
    You tried many samples and you failed. So what would give you another example? You should ask more detailed question. What do you have problem with? BTW, this tutorial looks nice: http://wiki.lazarus.freepascal.org/Multithreaded_Application_Tutorial – adf88 Aug 10 '10 at 16:33
  • 1
    And one more, sometimes it's better solution (because it's simpler) just to call Application.ProcessMessages from time to time inside your time-consuming process (e.g. just to update some progress bar or play some other animation showing to user that the application is busy). – adf88 Aug 10 '10 at 16:37
  • HI, adf88, I totally AGreed with you. But the problem with the samples is that I really do not undertand the whole thing about Thread, so I thought a Simple sample could make the things more clear for me. Any way, I solved the problem with Application.Processmessages, Thanks – Jose M. Vilomar Aug 10 '10 at 18:19
  • Here is a sample I have found useful: [Multi-Threading Delphi Database Queries](http://delphi.about.com/od/kbthread/a/query_threading.htm) – M Schenkel Aug 10 '10 at 17:12

2 Answers2

61

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.

himself
  • 4,806
  • 2
  • 27
  • 43
  • 2
    I know this just example code, but a comment stating what happens if the user clicks the button more than once would be useful. – Leonardo Herrera Aug 10 '10 at 18:23
  • Shouldn't that "TThread" in the latest code block definition be "MyWorkerThread: TMyWorkerThread" instead? – That Marc Jun 15 '16 at 22:14
  • @ThatMarc: The idea here was that you don't need to know what particular work TMyWorkerThread does to know that the thread is over. Any TThread descendant can be checked like that. – himself Jun 16 '16 at 13:20
  • 1
    Thanks for info. I wasn't trying to be all smartass here, but am dealing with threads first time, and just wanted to make sure I got that correctly... :) Thanks for the great post bdw! Helps a lot to better understand the threading generally... – That Marc Jun 16 '16 at 13:21
  • 1
    Btw, is there any other way that doesn't rely on the Windows function `WaitForSingleObject`, in order to target other platforms as well? – Redoman May 05 '17 at 17:00
  • Agree with jj about waitforsingleobj function – Gabriel Oct 03 '17 at 10:33
37

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

Jerry Abraham
  • 1,039
  • 18
  • 42