Search code examples
delphidelphi-xe6omnithreadlibrary

proper construction of ParallelTask (IOmniParallelTask) with cancellation & termination handler


I am just playing around with the OmniThreadLibrary after reading the docs, but I am still facing some simple/early problems on constructing a ParallelTask.

After the construction of a ParallelTask with cancellationToken and terminationHandler, terminationHandler.OnTerminated and OnStop are not being executed after the async execution is done and I was not able to find out why :-(

I hope some of the OTL pros can help me out on this one.

What I want to achieve:

  • execute an asynchronous operation
  • stop execution when cancelled (cancellationtoken)
  • execute some code in the mainthread (checking for exceptions) when the async operation is done

What I did so far:

After reading the docs I created a ParallelTask, setting up cancellationToken and terminationHandler via TaskConfig and executed the operation. The executed operation itself checks for the cancellationToken being signalled and does its work (here a Sleep of 1s). The HandleOnTerminated method checks for errors and sets the fIsDone and fHasError flags, getting read by someone from mainthread.

unit OTLSetup.Async;

interface

uses
  OtlParallel, OtlSync, OtlTaskControl, OtlTask;

type
  IAsyncOperation = interface
    ['{6B10AB46-DEB6-48F5-AC36-E9327AA54C82}']
    procedure Execute;
    procedure Cancel;

    function IsDone: boolean;
  end;

  TAsyncOperation = class(TInterfacedObject, IAsyncOperation)
  protected
    fParallelTask: IOmniParallelTask;
    fCancellationToken: IOmniCancellationToken;
    fIsDone: boolean;

    procedure HandleOnTerminated(const task: IOmniTaskControl);
    procedure HandleOnStop;
    procedure AsyncOperation(const task: IOmniTask);
  public
    procedure Execute;
    procedure Cancel;

    function IsDone: boolean;
  end;

implementation

uses
  Winapi.Windows;

{ TAsyncOperation }

procedure TAsyncOperation.Cancel;
begin
  fCancellationToken.Signal;
end;

procedure TAsyncOperation.Execute;
begin
  if Assigned(fParallelTask) then
    Exit;

  fIsDone := false;
  fCancellationToken := CreateOmniCancellationToken;
  fParallelTask := Parallel.ParallelTask;
  fParallelTask.NoWait.NumTasks(1);
  fParallelTask.TaskConfig(Parallel.TaskConfig.CancelWith(fCancellationToken).OnTerminated(HandleOnTerminated));
  fParallelTask.OnStop(HandleOnStop);
  fParallelTask.Execute(AsyncOperation);
end;

procedure TAsyncOperation.AsyncOperation(const task: IOmniTask);
var
  I: Integer;
begin
  for I := 0 to 5 do
    if task.CancellationToken.IsSignalled then
      Exit
    else
      Winapi.Windows.Sleep(1000);
end;

procedure TAsyncOperation.HandleOnStop;
begin
  fParallelTask := nil;
  fIsDone := true;
end;

procedure TAsyncOperation.HandleOnTerminated(const task: IOmniTaskControl);
begin
  fParallelTask := NIL;
  fIsDone := true;
end;

function TAsyncOperation.IsDone: boolean;
begin
  result := fIsDone;
end;

end.

With this peace of Code, fIsDone is never set, because HandleOnTerminate and HandleOnStopare never called. So with the exmaple from above the following ConsoleApplication seems to never end:

program OTLSetup;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  OTLSetup.Async in 'OTLSetup.Async.pas';

var
  LAsync: IAsyncOperation;
begin
  LAsync := TAsyncOperation.Create;
  try
    LAsync.Execute;

    while not LAsync.IsDone do
      Writeln('Async task still running');

    Writeln('Async task finished');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Solution

  • As posted in the comments, the issue I was facing has been caused by the consoleapplication itself, because it does not contain a messageloop (in my case a DUnitX project).

    Because the OTL communication seems to be based on windowsmessages, OnTerminated and OnStop are not triggered in absence of a working messageloop.