3

Unhandled exceptions within IOmniParallelTask execution should (as I understand the docs) be caught by the OTL and be attached to IOmniTaskControl instance, which may be accessed by the termination handler from IOmniTaskConfig.

So after setting up the IOmniParallelTask instance with a termination handler like this:

fTask := Parallel.ParallelTask.NoWait.NumTasks(1);
fTask.OnStop(HandleOnTaskStop);

fTask.TaskConfig(Parallel.TaskConfig.OnTerminated(HandleOnTaskThreadTerminated));
fTask.Execute(TaskToExecute);

any unhandled exceptions within TaskToExecute:

procedure TFormMain.TaskToExecute;
begin
  Winapi.Windows.Sleep(2000);
  raise Exception.Create('async operation exeption');
end;

should be attached to the IOmniTaskControl instance you get within the termination handler:

procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
  if not Assigned(task.FatalException) then
    Exit;

  memo.Lines.Add('an exception occured: ' + task.FatalException.Message);
end;

The issue at this point is, that the exception is not assigned to IOmniTaskControl.FatalException and I have no clue why.

Maybe some of you guys have some ideas on what I am doing wrong. The whole VCL sampleproject may be found here: https://github.com/stackoverflow-samples/OTLTaskException

codejanovic
  • 522
  • 5
  • 9

1 Answers1

3

This is an abstraction layer problem. Parallel.ParallelTask stores threaded code exception in a local field which is not synchronized with the IOmniTaskControl.FatalException property. (I do agree that this is not a good behaviour but I'm not yet sure what would be the best way to fix that.)

Currently the only way to access caught exception of an IOmniParallelTask object is to call its WaitFor method. IOmniParallelTask should really expose a FatalException/DetachException pair, just like IOmniParallelJoin. (Again, an oversight, which should be fixed in the future.)

The best way to solve the problem with the current OTL is to call WaitFor in the termination handler and catch the exception there.

procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
  try
    fTask.WaitFor(0);
  except
    on E: Exception do
      memo.Lines.Add('an exception occured: ' + E.Message);
  end;
  CleanupTask;
end;

I have also removed the HandleOnTaskStop and moved the cleanup to the termination handler. Otherwise, fTask was already nil at the time HandleOnTaskThreadTerminated was called.


EDIT

DetachException, FatalException, and IsExceptional have been added to the IOmniParallelTask so now you can simply do what you wanted in the first place (except that you have to use the fTask, not task).

procedure TFormMain.HandleOnTaskThreadTerminated(const task: IOmniTaskControl);
begin
  if not assigned(fTask.FatalException) then
    Exit;

  memo.Lines.Add('an exception occured: ' + FTask.FatalException.Message);
  CleanupTask;
end;

EDIT2

As noted in comments, OnTerminate handler relates to one task. In this example this is not a problem as the code makes sure that only one background task is running (NumTasks(1)).

In a general case, however, the OnStop handler should be used for this purpose.

procedure TFormMain.btnExecuteTaskClick(Sender: TObject);
begin
  if Assigned(fTask) then
    Exit;

  memo.Lines.Add('task has been started..');
  fTask := Parallel.ParallelTask.NoWait.NumTasks(1);
  fTask.OnStop(HandleOnStop);
  fTask.Execute(TaskToExecute);
end;

procedure TFormMain.HandleOnStop;
begin
  if not assigned(fTask.FatalException) then
    Exit;

  memo.Lines.Add('an exception occured: ' + FTask.DetachException.Message);
  TThread.Queue(nil, CleanupTask);
end;

As HandleOnStop is called in a background thread (because NoWait is used), CleanupTask must be scheduled back to the main thread, as in the original code.

gabr
  • 26,580
  • 9
  • 75
  • 141
  • just for the record: as I understood the given event workflow, `HandleOnTaskThreadTerminated` should already get executed by the thread that triggered the `IOmniParallelTask` (in this case the mainthread). so queueing `CleanupTask` should be needless, shouldnt it? – codejanovic Dec 13 '15 at 21:19
  • thank you for the fast fix. i think it should be also mentioned that the given approach using `IOmniParallelTask.FatalException` only works for this particular issue using only one thread (`.Numtasks(1)`). In all other cases the downside of this fix is, that you must also ensure `fTask` is still alive, as the `termination handler` is being called every time a thread terminates. In my case this is no problem, as I am internally using another abstraction on top of the OTL. – codejanovic Dec 14 '15 at 10:58
  • Indeed, you are correct. In this case it would be better to use OnStop handler, not OnTerminate handler. I'm adding this to the answer. – gabr Dec 14 '15 at 11:07