views:

639

answers:

2

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?

+9  A: 

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.

mghie
I saw that library yesterday, but I wanted to keep the code minimal, I think I will end up using it anyway. It's a simple .pas unit right?
Sebastian
Yes it is, only between 2and 3 kLOC, so it's fairly minimal already. I don't think this can be beat. The interface is extremely straightforward, all the hairy stuff is in the `implementation` part ;-) Just make sure you keep `IAsyncCall` references until the call has finished, otherwise the last reference count decrement will block.
mghie
It looks like this will be the best solution, thank you very much.
Sebastian
I tried AsyncCall(@MyFunction, 0);, LocalAsyncCall(@MyFunction); and LocalAsyncVclCall(@MyFunction, 0); MyFunction has a 20 seconds sleep() and both hangs the application for those 20 seconds, any idea what could be wrong?
Sebastian
I hinted at that possibility in my comment, but now I added an example to make it really clear.
mghie
+1  A: 

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.

Lobuno
I have a version of what I am doing in c# jeje, now that I am trying to translate into delphi (because I don't want to use .NET) I see what you are saying. Thanks.
Sebastian
I finally got it working with a simple BeginThread only, I don't really care about the result of the procedure, it just have to do something or Exit. Thanks for your msg.
Sebastian