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?
views:
639answers:
2If 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.
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.