Обработка исключений с 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 должен быть запланирован обратно в основной поток, как в исходном коде.

Другие вопросы по тегам