Обработка исключений с IOmniParallelTask не работает
Необработанные исключения внутри IOmniParallelTask
выполнение (как я понимаю документы) должно быть отслежено OTL и присоединено к IOmniTaskControl
экземпляр, к которому может получить доступ termination handler
от IOmniTaskConfig
,
Так что после настройки IOmniParallelTask
экземпляр с termination handler
как это:
fTask := Parallel.ParallelTask.NoWait.NumTasks(1);
fTask.OnStop(HandleOnTaskStop);
fTask.TaskConfig(Parallel.TaskConfig.OnTerminated(HandleOnTaskThreadTerminated));
fTask.Execute(TaskToExecute);
любые необработанные исключения в пределах TaskToExecute
:
procedure TFormMain.TaskToExecute;
begin
Winapi.Windows.Sleep(2000);
raise Exception.Create('async operation exeption');
end;
должны быть прикреплены к IOmniTaskControl
экземпляр вы попадаете в 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;
Проблема в том, что исключение не назначено IOmniTaskControl.FatalException
и я понятия не имею, почему.
Может быть, у некоторых из вас есть идеи о том, что я делаю неправильно. Весь образец проекта VCL можно найти здесь: https://github.com/stackru-samples/OTLTaskException
1 ответ
Это проблема уровня абстракции. Parallel.ParallelTask
хранит исключение для потокового кода в локальном поле, которое не синхронизировано с IOmniTaskControl.FatalException
имущество. (Я согласен, что это нехорошее поведение, но я пока не уверен, что будет лучшим способом исправить это.)
В настоящее время единственный способ получить доступ к пойманному исключению IOmniParallelTask
Цель состоит в том, чтобы назвать его WaitFor
метод. IOmniParallelTask
должен действительно выставить FatalException
/DetachException
пара, как и IOmniParallelJoin
, (Опять же, упущение, которое должно быть исправлено в будущем.)
Лучший способ решить проблему с текущим OTL - позвонить WaitFor
в обработчике завершения и поймать исключение там.
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;
Я также удалил HandleOnTaskStop
и перенес очистку в обработчик завершения. Иначе, fTask
уже nil
в это время HandleOnTaskThreadTerminated
назывался.
РЕДАКТИРОВАТЬ
DetachException
, FatalException
, а также IsExceptional
были добавлены к IOmniParallelTask
так что теперь вы можете просто сделать то, что вы хотели в первую очередь (за исключением того, что вы должны использовать fTask
не 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
Как отмечено в комментариях, OnTerminate
Обработчик относится к одной задаче. В этом примере это не проблема, так как код гарантирует, что выполняется только одна фоновая задача (NumTasks(1)
).
В общем случае, однако, OnStop
обработчик должен быть использован для этой цели.
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;
Как HandleOnStop
вызывается в фоновом потоке (потому что NoWait
используется), CleanupTask
должен быть запланирован обратно в основной поток, как в исходном коде.