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
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.