Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a function/procedure asynchronously in Delphi (Without components)

I am trying to run a function or procedure in Delphi asynchronously, but without using a component, is there a way to do it with delphi core functions?

like image 909
Sebastian Avatar asked Dec 02 '09 04:12

Sebastian


2 Answers

You may also want to execute your procedure on a thread. Use then the OnTerminate event to get the result. Yes, in these days of .NET and C# we are some kind of spoiled with the easy and convenient form of executing methods asynchronioulsly, but that's the way it works on Delphi.

like image 53
Lobuno Avatar answered Nov 06 '22 23:11

Lobuno


If you are asking whether the VCL has something like BeginInvoke in .NET out-of-the-box, then the answer is no. However, you can get something quite similar in the form of a small unit that you link to your program, the AsyncCalls library by Andreas Hausladen. It's not a component, so I guess it qualifies. It also supports Delphi from version 5 onwards. Very much recommended.

Edit:

I'll add an example since you didn't get it running. If you get blocking in your calling code then your problem is that no reference is kept to the IAsyncCall interface pointer that the function returned. The object implementing the interface will therefore be destroyed immediately when the temporary reference goes out of scope. The destructor will be called in the context of the VCL thread, and it will call WaitForSingleObject() or a similar function to wait for the worker thread to finish. The result of that is that your VCL thread blocks.

You will get the correct behaviour if you maintain a reference to the interface pointer:

type
  TForm1 = class(TForm)
    Button1: TButton;
    Timer1: TTimer;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    fAsyncCall: IAsyncCall;
    procedure WaitForIt(ADelay: integer);
  end;

Set the timer to be disabled and let it have a very short Interval, say 50 ms. The button click starts the asynchronous operation:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.Enabled := FALSE;
  fAsyncCall := AsyncCall(WaitForIt, 1000);
end;

procedure TForm1.WaitForIt(ADelay: integer);
begin
  Sleep(ADelay);

  EnterMainThread;
  try
    Randomize;
    Color := RGB(Random(256), Random(256), Random(256));
    Timer1.Enabled := TRUE;
  finally
    LeaveMainThread;
  end;
end;

While the operation is active no other can be started. On completion it enables the timer to notify the form and reset the interface reference:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := FALSE;
  Assert((fAsyncCall <> nil) and fAsyncCall.Finished);
  fAsyncCall := nil;
  Button1.Enabled := TRUE;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := (fAsyncCall = nil) or fAsyncCall.Finished;
end;

Note how it is even possible to access the form directly from the called method, by using EnterMainThread() and LeaveMainThread().

Above code is not the absolute minimum, it is intended to demonstrate some ideas only.

like image 28
mghie Avatar answered Nov 07 '22 01:11

mghie