IOmniParallelTask​​ 的异常处理不起作用

Exceptionhandling with IOmniParallelTask not working

IOmniParallelTask 执行中未处理的异常应该(据我了解文档)被 OTL 捕获并附加到 IOmniTaskControl 实例,termination handler 可以从IOmniTaskConfig.

因此,在使用 termination handler 设置 IOmniParallelTask 实例后,如下所示:

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;

应该附加到您在 termination handler:

中获得的 IOmniTaskControl 实例
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/Whosebug-samples/OTLTaskException

这是一个抽象层问题。 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 并将清理移至终止处理程序。否则,在调用 HandleOnTaskThreadTerminatedfTask 已经是 nil


编辑

DetachExceptionFatalExceptionIsExceptional 已添加到 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;

编辑2

如评论中所述,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必须像原来的代码一样调度回主线程。