AsyncCalls - asynchronous function calls
With AsyncCalls you can execute multiple functions at the same time and synchronize them at every point in
the function or method that started them. This allows you to execute time consuming code whos result is needed
at a later time in a different thread. While the asynchronous function is executed the caller function can do
other tasks.
The AsyncCalls unit offers a variety of function prototypes to call asynchronous functions. There are functions
that can call asynchron functions with one single parameter of the type: TObject, Integer, AnsiString, WideString,
IInterface, Extended and Variant. Another function allows you to transfer a user defined value type (record) to the asynchron
function where it can be modify. And there are functions that can call asynchron functions with a variable number of
arguments. The arguments are specified in an const array of const and are automatically
mapped to normal function arguments.
Inlined VCL/main thread synchronization is supported starting with version 2.0. With this you can implement
the code that calls a VCL function directly in your thread method without having to use a helper method and
TThread.Synchronize. You have full access to all local variables.
Version 2.9 introduces the TAsyncCalls class that utilizes generics and anonymous methods (Delphi 2009 or newer).
Download
AsyncCalls.zip Version 2.99 (34 KB)
License
The AsyncCalls unit is licensed under the Mozilla Public Licence ("MPL") version 1.1.
Installation
Extract the AsyncCalls.zip to a directory of your choice. Add the AnyncCalls.pas unit to your project and uses statements.
Requirements
Works with Delphi 5, 6, 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE, XE2
Changelog
- Version: 2.99 (2011-12-14):
Added: IAsyncCall.CancelInvocation method
Added: IAsyncCall.Forget method
- Version: 2.98 (2011-10-22):
Added: Support for RAD Studio XE 64 bit
- Version: 2.97 (2011-05-21):
Fixed: Replaced Suspend/Resume code to prevent a race condition where all threads are suspended but their FSuspended flag is false.
Fixed: Exception handling in TAsyncCall.InternExecuteSyncCall. Quit wasn't called after an exception was raised.
- Version: 2.96 (2010-09-12):
Fixed: CoInitialize call was missing
- Version: 2.95 (2010-09-12):
Added: Support for RAD Studio XE
Added: Support for UnicodeString
- Version: 2.92 (2009-08-30):
Added: Support for RAD Studio 2010
Restored: Delphi 2009 Update 1 fixed the compiler bug. All generic methods are now available.
- Version 2.91 (2008-09-29):
Fixed: All generic methods are now disabled due to an internal compiler error in Delphi 2009
- Version 2.9 (2008-09-27):
Fixed: Window message handling
Added: Delphi 2009 support with generics and anonymous methods
Added: AsyncCall(Runnable: IAsyncRunnable)
- Version 2.21 (2008-05-14):
Fixed: Bug in AsyncMultiSync
- Version 2.2 (2008-05-12):
Fixed: Bugs in main thread AsyncMultiSync implementation
Added: Delphi 5 support
- Version 2.1 (2008-05-06):
Added: Delphi 6 support
Added: Support for "Exit;" in the MainThread block
Fixed: Exception handling for Delphi 6, 7 and 2005
Fixed: EBX, ESI and ESI are now copied into the synchronized block (Self-Pointer)
- Version 2.0 (2008-05-04):
Added: EnterMainThread/LeaveMainThread
Added: LocalVclCall, LocalAsyncVclCall, MsgAsyncMultiSync
Added: LocalAsyncExec, AsyncExec
Added: IAsyncCall.ForceDifferentThread
Fixed: Exception handling
Removed: Delphi 5 and 6 support
- Version 1.2 (2008-02-10):
Added CoInitialize for the threads
LocalAsyncCall function
Exception handling
- Version 1.1 (2007-08-14):
Workaround for TThread.Resume bug
- Version 1.0 (2006-12-23):
Fixed: Exception where Thread was destroyed while the finalization code accessed it.
Example
procedure TForm1.Button3Click(Sender: TObject);
var
Value: Integer;
begin
TAsyncCalls.Invoke(procedure
begin
Value := 10;
TAsyncCalls.VCLInvoke(procedure
begin
ShowMessage('The value may not equal 10: ' + IntToStr(Value));
end);
Value := 20;
TAsyncCalls.VCLSync(procedure
begin
ShowMessage('The value equals 20: ' + IntToStr(Value));
end);
Value := 30;
end);
Sleep(1000);
end;
{ The cdecl function GetFiles() has two arguments, a string and an object which are declared like normal arguments. }
procedure GetFiles(const Directory: string; Filenames: TStrings); cdecl;
var
h: THandle;
FindData: TWin32FindData;
begin
h := FindFirstFile(PChar(Directory + '\*.*'), FindData);
if h <> INVALID_HANDLE_VALUE then
begin
repeat
if (StrComp(FindData.cFileName, '.') <> 0) and (StrComp(FindData.cFileName, '..') <> 0) then
begin
Filenames.Add(Directory + '\' + FindData.cFileName);
if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
GetFiles(Filenames[Filenames.Count - 1], Filenames);
end;
until not FindNextFile(h, FindData);
Windows.FindClose(h);
end;
end;
procedure TFormMain.ButtonGetFilesClick(Sender: TObject);
var
Dir1, Dir2, Dir3: IAsyncCall;
Dir1Files, Dir2Files, Dir3Files: TStrings;
begin
Dir1Files := TStringList.Create;
Dir2Files := TStringList.Create;
Dir3Files := TStringList.Create;
ButtonGetFiles.Enabled := False;
try
{ Call the cdecl function GetFiles() with two arguments, a string and an object. }
Dir1 := AsyncCall(@GetFiles, ['C:\Windows', Dir1Files]);
{ Call the cdecl function GetFiles() with two arguments, a string and an object. }
Dir2 := AsyncCall(@GetFiles, ['D:\Html', Dir2Files]);
{ Call the cdecl function GetFiles() with two arguments, a string and an object. }
Dir3 := AsyncCall(@GetFiles, ['E:', Dir3Files]);
{ Wait until both async functions have finished their work. While waiting make the UI reacting on user interaction. }
while AsyncMultiSync([Dir1, Dir2], True, 10) = WAIT_TIMEOUT do
Application.ProcessMessages;
Dir3.Sync; // Force the Dir3 function to finish here
MemoFiles.Lines.Assign(Dir1Files);
MemoFiles.Lines.AddStrings(Dir2Files);
MemoFiles.Lines.AddStrings(Dir3Files);
finally
ButtonGetFiles.Enabled := True;
Dir3Files.Free;
Dir2Files.Free;
Dir1Files.Free;
end;
end;
IAsyncCall interface
All AsyncCall functions return an IAsyncCall interface that allows to synchronize the functions.
IAsyncCall = interface
function Sync: Integer;
function Finished: Boolean;
function ReturnValue: Integer;
function Canceled: Boolean;
procedure ForceDifferentThread;
procedure CancelInvocation;
procedure Forget;
end;
- The Sync method waits until the function is finished and returns the return value of the function which is
undefined for procedures.
- The Finished method returns True when the asynchron function is finished. Otherwise it returns False.
- The ReturnValue method returns the asynchron function's return value which is undefined for procedures. If
the asynchron function is still executed ReturnValue raises an EAsyncCallError exception.
- The Canceled method returns True if the AsyncCall was canceled by CancelInvocation.
- The ForceDifferentThread method tells AsyncCalls that the assigned function must
not be executed in the current thread.
- The CancelInvocation method stopps the AsyncCall from being invoked. If the AsyncCall is already
processed, a call to CancelInvocation has no effect and the Canceled function will
return False as the AsyncCall wasn't canceled.
- The Forget method unlinks the IAsyncCall interface from the internal AsyncCall. This means that
if the last reference to the IAsyncCall interface is gone, the asynchronous call will
be still executed. The interface's methods will throw an exception if called after calling
Forget. The async function must not call into the main thread because it could be executed
after the TThread.Synchronize/Queue mechanism was shut down by the RTL what can cause a
dead lock.
LocalAsyncCall function
LocalAsyncCall() executes the given local function/procedure in a separate thread.
The result value of the asynchronous function is returned by IAsyncCall.Sync() and
IAsyncCall.ReturnValue().
The LocalAsyncExec() function calls the IdleMsgMethod while the local procedure is
executed.
function LocalAsyncCall(LocalProc: TLocalAsyncProc): IAsyncCall;
function LocalAsyncCallEx(LocalProc: TLocalAsyncProcEx; Param: INT_PTR): IAsyncCall;
procedure LocalAsyncExec(Proc: TLocalAsyncProc; IdleMsgMethod: TAsyncIdleMsgMethod);
- LocalProc: A local function that should be executed asynchron.
Example
procedure MainProc(const S: string);
var
Value: Integer;
a: IAsyncCall;
function DoSomething: Integer;
begin
if S = 'Abc' then
Value := 1;
Result := 0;
end;
begin
a := LocalAsyncCall(@DoSomething);
// do something
a.Sync;
LocalAsyncExec(@DoSomething, Application.ProcessMessages);
end;
VCL synchronization
LocalVclCall() executes the given local function/procedure in the main thread. It
uses the TThread.Synchronize function which blocks the current thread.
LocalAsyncVclCall() execute the given local function/procedure in the main thread.
It does not wait for the main thread to execute the function unless the current
thread is the main thread. In that case it executes and waits for the specified
function in the current thread like LocalVclCall().
The result value of the asynchronous function is returned by IAsyncCall.Sync() and
IAsyncCall.ReturnValue().
procedure LocalVclCall(LocalProc: TLocalVclProc; Param: INT_PTR = 0);
function LocalAsyncVclCall(LocalProc: TLocalVclProc; Param: INT_PTR = 0): IAsyncCall;
Example
procedure TForm1.MainProc;
procedure DoSomething;
procedure UpdateProgressBar(Percentage: Integer);
begin
ProgressBar.Position := Percentage;
Sleep(20); // This delay does not affect the time for the 0..100 loop
// because UpdateProgressBar is non-blocking.
end;
procedure Finished;
begin
ShowMessage('Finished');
end;
var
I: Integer;
begin
for I := 0 to 100 do
begin
// Do some time consuming stuff
Sleep(30);
LocalAsyncVclCall(@UpdateProgressBar, I); // non-blocking
end;
LocalVclCall(@Finished); // blocking
end;
var
a: IAsyncCall;
begin
a := LocalAsyncCall(@DoSomething);
a.ForceDifferentThread; // Do not execute in the main thread because this will
// change LocalAyncVclCall into a blocking LocalVclCall
// do something
//a.Sync; The Compiler will call this for us in the Interface._Release method
end;
EnterMainThread/LeaveMainThread
EnterMainThread/LeaveMainThread can be used to temporary switch to the
main thread. The code that should be synchonized (blocking) has to be put
into a try/finally block and the LeaveMainThread() function must be called
from the finally block. A missing try/finally will lead to an access violation.
procedure EnterMainThread;
procedure LeaveMainThread;
- All local variables can be used. (EBP points to the thread's stack while
ESP points the the main thread's stack)
- Unhandled exceptions are passed to the surrounding thread.
- The integrated Debugger is not able to follow the execution flow. You have
to use break points instead of "Step over/in".
- Nested calls to EnterMainThread/LeaveMainThread are ignored. But they must
strictly follow the try/finally structure.
Example
procedure MyThreadProc;
var
S: string;
begin
Assert(GetCurrentThreadId <> MainThreadId);
S := 'Hallo, I''m executed in the main thread';
EnterMainThread;
try
Assert(GetCurrentThreadId = MainThreadId);
ShowMessage(S);
finally
LeaveMainThread;
end;
Assert(GetCurrentThreadId <> MainThreadId);
end;
AsyncCall functions
The AsyncCall() functions start a specified asynchronous function.
The AsyncExec() function calls the IdleMsgMethod in a loop, while the async.
method is executed.
function AsyncCall(Proc: TAsyncCallArgObjectProc; Arg: TObject): IAsyncCall; overload;
function AsyncCall(Proc: TAsyncCallArgIntegerProc; Arg: Integer): IAsyncCall; overload;
function AsyncCall(Proc: TAsyncCallArgStringProc; const Arg: string): IAsyncCall; overload;
function AsyncCall(Proc: TAsyncCallArgWideStringProc; const Arg: WideString): IAsyncCall; overload;
function AsyncCall(Proc: TAsyncCallArgInterfaceProc; const Arg: IInterface): IAsyncCall; overload;
function AsyncCall(Proc: TAsyncCallArgExtendedProc; const Arg: Extended): IAsyncCall; overload;
function AsyncCallVar(Proc: TAsyncCallArgVariantProc; const Arg: Variant): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgObjectMethod; Arg: TObject): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgIntegerMethod; Arg: Integer): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgStringMethod; const Arg: string): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgWideStringMethod; const Arg: WideString): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgInterfaceMethod; const Arg: IInterface): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgExtendedMethod; const Arg: Extended): IAsyncCall; overload;
function AsyncCallVar(Method: TAsyncCallArgVariantMethod; const Arg: Variant): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgObjectEvent; Arg: TObject): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgIntegerEvent; Arg: Integer): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgStringEvent; const Arg: string): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgWideStringEvent; const Arg: WideString): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgInterfaceEvent; const Arg: IInterface): IAsyncCall; overload;
function AsyncCall(Method: TAsyncCallArgExtendedEvent; const Arg: Extended): IAsyncCall; overload;
function AsyncCallVar(Method: TAsyncCallArgVariantEvent; const Arg: Variant): IAsyncCall; overload;
procedure AsyncExec(Method: TNotifyEvent; Arg: TObject; IdleMsgMethod: TAsyncIdleMsgMethod);
- Proc/Method: Function that should be executed asynchron.
- Arg: User defined argument that is copied to the asynchron function argument.
Example
function TestFunc(const Text: string): Integer;
begin
Result := TimeConsumingFuncion(Text);
end;
a := AsyncCall(TestFunc, 'A Text');
AsyncCallEx functions
The AsyncCallEx() functions start a specified asynchronous function with a referenced value type (record) that can
be manipulated in the asynchron function.
function AsyncCallEx(Proc: TAsyncCallArgRecordProc; var Arg{: TRecordType}): IAsyncCall; overload;
function AsyncCallEx(Method: TAsyncCallArgRecordMethod; var Arg{: TRecordType}): IAsyncCall; overload;
function AsyncCallEx(Method: TAsyncCallArgRecordEvent; var Arg{: TRecordType}): IAsyncCall; overload;
- Proc/Method: Function that should be executed asynchron.
- Arg: User defined value type (record).
Example
type
TData = record
Value: Integer;
end;
procedure TestRec(var Data: TData);
begin
Data.Value := 70;
end;
a := AsyncCallEx(@TestRec, MyData);
{ Don't access "MyData" here until the async. function has finished. }
a.Sync; // MyData.Value is now 70
AsyncCall functions with a variable number of arguments
This AsyncCall() functions start a specified asynchronous function with a variable number of argument. The asynchron function must be declared as
cdecl and the argument's modifier must be const for Variants. All other types can have the
const modified but it is not necessary.
function AsyncCall(Proc: TCdeclFunc; const Args: array of const): IAsyncCall; overload;
function AsyncCall(Proc: TCdeclMethod; const Args: array of const): IAsyncCall; overload;
- Proc/Method: Function that should be executed asynchron.
- Args: An open array that specifies the asynchron function arguments. The values are either copied or the reference counter
is increased during the execution of the asynchron function.
AsyncMultiSync
AsyncMultiSync() waits for the async calls and other handles to finish.
MsgAsyncMultiSync() waits for the async calls, other handles and the message queue.
function AsyncMultiSync(const List: array of IAsyncCall; WaitAll: Boolean = True;
Milliseconds: Cardinal = INFINITE): Cardinal;
function AsyncMultiSyncEx(const List: array of IAsyncCall; const Handles: array of THandle;
WaitAll: Boolean = True; Milliseconds: Cardinal = INFINITE): Cardinal;
function MsgAsyncMultiSync(const List: array of IAsyncCall; WaitAll: Boolean;
Milliseconds: Cardinal; dwWakeMask: DWORD): Cardinal;
function MsgAsyncMultiSyncEx(const List: array of IAsyncCall; const Handles: array of THandle;
WaitAll: Boolean; Milliseconds: Cardinal; dwWakeMask: DWORD): Cardinal;
Arguments
List |
An array of IAsyncCall interfaces for which the function should wait. |
Handles |
An array of THandle for which the function should wait. |
WaitAll = True |
The function returns when all listed async calls have
finished. If Milliseconds is INFINITE the async calls
meight be executed in the current thread.
The return value is zero when all async calls have finished.
Otherwise it is WAIT_FAILED. |
WaitAll = False |
The function returns when at least one of the async calls
has finished. The return value is the list index of the
first finished async call. If there was a timeout, the
return value is WAIT_FAILED. |
Milliseconds |
Specifies the number of milliseconds to wait until a
timeout happens. The value INFINITE lets the function wait
until all async calls have finished. |
dwWakeMask |
see Windows.MsgWaitForMultipleObjects() |
Limitations
Length(List)+Length(Handles) must not exceed MAXIMUM_ASYNC_WAIT_OBJECTS (61 elements).
Return value
- WAIT_TIMEOUT
The function timed out
- WAIT_OBJECT_0+index
The first finished async call
- WAIT_OBJECT_0+Length(List)+index
The first signaled handle
- WAIT_OBJECT_0+Length(List)+Length(Handles)
A message was signaled
- WAIT_ABANDONED_0+index
The abandoned async call
- WAIT_ABANDONED_0+Length(List)+index
The abandoned handle
- WAIT_FAILED
The function failed
AsyncCalls Internals - Thread pool and waiting-queue
An execution request is added to the waiting-queue when an async. function is started. This request forces the thread pool to
check if there is an idle/suspended thread that could do the job. If such a thread exists, it is reactivated/resumed. If no thread
is available then it depends on the number of threads in the pool what happens. If the maximum thread number is already reached the
request remains in the waiting-queue. Otherwise a new thread is added to the thread pool.
Threads that aren't idle/suspended take the oldest request from the waiting-queue an execute the associated async. function. If the
waiting queue is empty the threads becomes idle/suspended.