delphi 执行任务中操作的例程,显示加载屏幕,而不冻结主窗体

u0sqgete  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(140)

我希望构建一个例程,负责通过过程(Tproc)使用任务执行任何操作,同时显示加载屏幕,而不会冻结系统的主窗体。一个额外的细节是,它还能够捕获操作可能产生的可能异常。
我构建了下面的代码,它工作得很好,但有时会出错,加载屏幕最终无法关闭,始终存在于屏幕上。
任何建议都欢迎。谢谢。

type
  TLoadingClass= class
  strict private
    class var FForm: TForm;

  public
    class procedure ActionAndWait(Action: Tproc);
  end;

class procedure TLoadingClass.ActionAndWait(Action: Tproc);
var
  aTask: ITask;
  vException: Pointer;
begin
  vException := nil;

  FForm := TLoadingForm.Create(nil);
  try
    aTask := TTask.Run(procedure
      begin
        try
          try
            Action; {Run Action}
          except on E: Exception do
            vException := @E {Capture Exception}
          end
        finally
          while not FForm.Showing do {Wait for the form to be created, if the action is very quick.}
            Sleep(1);
          TLoadingForm(FForm).Hide;
          TLoadingForm(FForm).Close;
        end;
      end);
    TLoadingForm(FForm).ShowModal; {Show the loading form}
  finally
    TTask.WaitForAll(aTask);
    FreeAndNil(FForm);
    if Assigned(vException) then
      raise Exception(@vException);
  end;
end;

字符串
调用示例

TLoadingClass.ActionAndWait(
  procedure
  begin
    try
      Sleep(5000);
      raise Exception.Create('Test');
    except on E: Exception do
      ShowMessage(E.Message);
    end;
  end);

y0u0uwnf

y0u0uwnf1#

你的代码有几个问题。
首先,你从后台线程访问UI(表单),这是你永远不应该做的。这样的代码总是需要与主线程同步。
接下来,你没有正确处理任务异常。异常对象是由编译器自动处理的,你不能只是抓取一个指向异常对象的指针并在以后使用它。整个try...except在任务方法中是无用的。如果任务中有未处理的异常,WaitWaitForAll将引发EAggregatedException,您可以在那里捕获该异常并处理其内部异常。如果有多个任务引发异常,则会有多个内部异常。
接下来,您已经在传递给ActionAndWait的匿名方法中捕获了异常,因此捕获的异常不会作为EAgreggatedException传播。ActionAndWait中的ShowMessage将在后台线程的上下文中运行,如果您想从那里使用UI,它还需要与主线程同步。
您在TLoadingClass中将FForm声明为字段。最好删除该字段并使用本地变量,就像您在任务中使用的那样。当您调用HideClose时,也没有必要将FForm类型转换为TLoadingForm。此外,调用Close就足够了,因为它也会隐藏表单。
经过清理和更正的代码看起来像这样:

class procedure TLoadingClass.ActionAndWait(Action: Tproc);
var
  LTask: ITask;
  LForm: TLoadingForm;
begin
  try
    LTask := TTask.Create(procedure
      begin
        try
          Action;
        finally
          TThread.Queue(nil,
            procedure
            begin
              LForm.Close;
            end);
        end;
      end);
    LForm := TLoadingForm.Create(nil);
    try
      try
        LTask.Start;
        LForm.ShowModal;
      finally
        TTask.WaitForAll(LTask);
      end;
    finally
      LForm.Free;
    end;
  except
    on E: EAggregateException do
      for var i := 0 to E.Count - 1 do
        ShowMessage(E.InnerExceptions[i].Message);
    on E: Exception do
      ShowMessage(E.Message);
  end;
end;

字符串
调用示例:

TLoadingClass.ActionAndWait(
  procedure
  begin
    Sleep(5000);
    raise Exception.Create('Test');
  end);

相关问题