5

My database is in a VPS and I should get some query from my tables

Because of getting query from server taking long time ( depending on Internet speed ! ) , I want to use threads to get queries

Now I create a thread and get query and then send result to my forms with sending and handling messages

I want to know is it possible to create and use a thread locally ?!?

My mean is :

procedure Requery;
var
 ...
begin
 Create Thread;
 ...

 Pass my Query Component to Thread
 ...

 Getting Query in Thread;
 ...

 Terminate and Free Thread
 ...

 Do next jobs with Query;
 ...

end;

The main part is last part ( Do next jobs ... ) , I dont want to use query result in a message handler and I want to use them in the same procedure and after thread job

Is it possible ?!

I think this is not possible with Delphi TThread class and I should use other threading techniques ...

  • I`m using Delphi XE6
Agustin Seifert
  • 1,938
  • 1
  • 16
  • 29
Mahmoud_Mehri
  • 1,643
  • 2
  • 20
  • 38

3 Answers3

14

What you describe is not the best use of a thread. The calling code is blocked until the thread is finished. That negates the use of running code in parallel at all. You could just perform the query directly instead:

procedure Requery;
var
  ...
begin
  ...
  // run query
  // do next jobs with query
  ...
end;

That being said, since you are using XE6, you can create a "local" thread by using the TThread.CreateAnonymousThread() method, specifying an anonymous procedure that "captures" the variables you want it to work with, eg:

procedure Requery;
var
  Event: TEvent;
  H: THandle;
begin
  Event := TEvent.Create;
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        try
          // run query in thread
        finally
          Event.SetEvent;
        end;
      end
    ).Start;
    H := Event.Handle;
    while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) do
      Application.ProcessMessages;
  finally
    Event.Free;
  end;

  // Do next jobs with query
  ...
end;

Alternatively:

procedure Requery;
var
  Thread: TThread;
  H: THandle;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      // run query in thread
    end
  );
  try
    Thread.FreeOnTerminate := False;
    H := Thread.Handle;
    Thread.Start;
    while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) do
      Application.ProcessMessages;
  finally
    Thread.Free;
  end;

  // Do next jobs with query
  ...
end;

However, threading is more useful when you let it run in the background while you do other things and then you act when the thread has finished its work. For example:

procedure TMyForm.Requery;
var
  Thread: TThread;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      // run query in thread
    end
  );
  Thread.OnTerminate := QueryFinished;
  Thread.Start;
end;

procedure TMyForm.QueryFinished(Sender: TObject);
begin
  if TThread(Sender).FatalException <> nil then
  begin
    // something went wrong
    Exit;
  end;
  // Do next jobs with query
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • In delphi Xe8 the MsgWaitForMultipleObjects with parameter Event.Handle triggers error "Constant object cannot be passed as var parameter" . Do you maybe have an updated version / option of it? Thanks. – That Marc Jun 22 '17 at 12:45
  • Ps: There is missing " ; " at end of latest procedure in last example, and "begin" is written wrong (being). * – That Marc Jun 22 '17 at 13:33
  • @ThatMarc I don't see any missing `;`. You don't use `;` at the end of an anonymous procedure passed as a parameter. As for the compiler errors, I have updated the example. – Remy Lebeau Jun 22 '17 at 14:24
  • I meant after "procedure TMyForm.QueryFinished(Sender: TObject)", before begin.. Of course IDE reports that right away, but just for a heads up on example. Nothing bad meant. :) Thanks for updated example!! – That Marc Jun 26 '17 at 10:33
  • @ThatMarc fixed – Remy Lebeau Oct 07 '20 at 22:12
0

I think that using a thread this way isn't a good idea, but the answer is yes. You can do it.

procedure LocalThread;
var
  LThread: TCustomThread; //Your thread class
  LThreadResult: xxxxxxx//Your result type
begin
  LThread := TCustomThread.Create(True);
  try
    //Assign your properties

    LThread.Start;

    //Option A: blocking
    LThread.WaitFor;

    //Option B: non blocking
    while not LThread.Finished do
    begin
      Sleep(xx);
      //Some progress here ??
    end;

    //Here query your thread for your result property
    LThreadResult := LThread.MyResultProperty;
  finally
    LThread.Free;
  end

  //Do next jobs with LThreadResult
end;
Agustin Seifert
  • 1,938
  • 1
  • 16
  • 29
  • thanks, but in this way program will crash until thread end job , then it`s better to do without thread ! , In fact the query block of code should execute in a thread and application wait to end querying without crashing – Mahmoud_Mehri Sep 20 '15 at 14:33
  • 5
    Your program doesn't crash, Mahmood. It hangs. (Crashing means it abruptly stops running. Hanging means it's still running, but appears to be stuck making no progress. *Appears*.) You can avoid hanging by processing messages. Processing messages in this context is generally a sign of a bad design, but if you *really* need to send this work to another thread within a single function, then you've decided that you *want* a bad design, so go ahead and process messages while you wait for the thread to finish. – Rob Kennedy Sep 20 '15 at 15:02
0

Yes you can do that.

The way I would do it is to add an event-handler to your form.
You'll have to link the event-handler in code, but that's not that difficult.

Create a thread like so:

TMyEventHandler = procedure(Sender: TObject) of object;

type
  TMyThread = class(TThread)
  strict private
    FDoneEvent: TMyEvent;
    FDone: boolean;
    FQuery: TFDQuery;
    constructor Create(DoneEvent: TMyEventHandler; Query: TFDQuery);
    procedure Execute; override;
    function GetQuery: TFDQuery;
  public
    property Query read GetQuery;
  end;

  TForm1 = class(TForm)
    FDQuery1: TFDQuery;  //Do not connect the FDQuery1 to anything!
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
  private
    FOnThreadDone: TMyEventHandler;
    FMyThread: TMyThread;
    procedure DoThreadDone;
    procedure ThreadDone;
  public
    property OnThreadDone: TMyEventHandler read FOnThreadDone write FOnThreadDone;
  ....

implementation

constructor TMyThread.Create(DoneEvent: TMyEvent; Query: TFDQuery);
begin
  inherited Create(true);
  FDoneEvent:= DoneEvent;
  FQuery:= Query;
  Start;
end;

procedure TMyThread.Execute;
begin
  //Do whatever with the query
  //when done do:
  FDone:= true;
  Synchonize(Form1.DoThreadDone);
end;

function TMyThread.GetQuery: TFDQuery;
begin
  if not Done then Result:= nil else Result:= FQuery;
end;

procedure TForm1.DoThreadDone;
begin
  if Assigned(FOnThreadDone) then FOnThreadDone(Self);
end;

procedure TForm1.ThreadDone(Sender: TObject);
begin
  ShowMessage('Query is done');
  //Now you can display the result of the query, by wiring it
  //to a dataset.
  MyDataSource1.Dataset:= FMyThread.Query;
  FMyThread.Free;
end;

procedure TForm1.StartTheQuery;
begin
  OnThreadDone:= Self.ThreadDone;
  FMyThread:= TMyThread.Create(OnThreadDone, FDQuery1);
end;

Now the query will run in the background and signal your event handler when it is done. Meanwhile you can do all the mousing around and user interaction you want without having to worry. Note that you cannot use FDQuery1 at all whilst the thread is using it, and you cannot have FDQuery1 wired to a DataSource whilst it's the thread is running with it.
Leave it unwired and wire it in the ThreadDone event handler as shown.

Johan
  • 74,508
  • 24
  • 191
  • 319