Async is the simplest of high-level abstractions and is typically used for fire and forget scenarios. To create an Async task, call Parallel.Async.
When you call
Parallel.Async, code is started in a new thread (indicated by the bold vertical line) and both main and background threads continue execution. At some time, background task completes execution and disappears.
See also demo
Parallel.Async( procedure begin MessageBeep($FFFFFFFF); end);
This simple program creates a background task with a sole purpose to make some noise from it. The task is coded as an anonymous method but you can also use a normal method or a normal procedure for the task code.
Parallel class defines two
Async overloads. The first accepts a parameter-less background task and an optional task configuration block and the second accepts a background task with an IOmniTask parameter and an optional task configuration block.
type TOmniTaskDelegate = reference to procedure(const task: IOmniTask); Parallel = class class procedure Async(task: TProc; taskConfig: IOmniTaskConfig = nil); overload; class procedure Async(task: TOmniTaskDelegate; taskConfig: IOmniTaskConfig = nil); overload; ... end;
The second form is useful if the background code needs access to the IOmniTask interface, for example to send messages to the owner or to execute code in the owner thread (typically that will be the main thread).
The example below uses Async task to fetch the contents of a web page (by calling a mysterious function HttpGet) and then uses Invoke to execute a code that logs the length of the result in the main thread.
Parallel.Async( procedure (const task: IOmniTask) var page: string; begin HttpGet('otl.17slon.com', 80, 'tutorials.htm', page, ''); task.Invoke( procedure begin lbLogAsync.Items.Add(Format('Async GET: %d ms; page length = %d', [time, Length(page)])) end); end);
The same result could be achieved by sending a message from the background thread to the main thread. TaskConfig block is used to configure message handler.
const WM_RESULT = WM_USER; procedure LogResult(const task: IOmniTaskControl; const msg: TOmniMessage); begin lbLogAsync.Items.Add(Format('Async GET: %d ms; page length = %d', [time, Length(page)])) end; Parallel.Async( procedure (const task: IOmniTask) var page: string; begin HttpGet('otl.17slon.com', 80, 'tutorials.htm', page, ''); task.Comm.Send(WM_RESULT, page); end, TaskConfig.OnMessage(WM_RESULT, LogResult) );
Let me warn you that in cases where you want to return a result from a background task, Async abstraction is not the most appropriate. You would be better off by using a Future.
If the background code raises unhandled exception, OmniThreadLibrary will catch this exception and re-raise it in the
OnTerminated handler. This way the exception will travel from the background thread to the owner thread where it can be processed.
OnTerminated handler occurs at the unspecified moment when Windows are processing window messages, there is no good way to catch this message with a try..except block. The caller must install its own
OnTerminated handler instead and handle exception there.
The following example uses
OnTerminated handler to detach fatal exception from the task, log the exception details and destroy the exception object.
Parallel.Async( procedure begin Sleep(1000); raise Exception.Create('Exception in Async'); end, Parallel.TaskConfig.OnTerminated( procedure (const task: IOmniTaskControl) var excp: Exception; begin if assigned(task.FatalException) then begin excp := task.DetachException; Log('Caught async exception %s:%s',[excp.ClassName, excp.Message]); FreeAndNil(excp); end; end ));
If you don't install a
OnTerminated handler, exception will be handled by the application-level filter, which will by default cause a message box to appear.
See also demo