Multithreading 在长时间任务中保持应用程序的响应性

Multithreading 在长时间任务中保持应用程序的响应性,multithreading,delphi,callback,Multithreading,Delphi,Callback,应用程序中的某个表单显示模型的图形视图。用户可以在其他负载中启动模型转换,这可能需要相当长的时间。这种转换有时在没有任何用户交互的情况下进行,有时需要频繁的用户输入。除非需要用户输入,否则应禁用UI(仅显示进度对话框) 可能的办法: 忽略这个问题,只需将转换代码放在一个过程中并调用它。不好,因为在转换需要一些时间但不需要用户输入的情况下,应用程序似乎挂起 在代码中撒上回调:这很麻烦-你必须在转换代码中加入大量的调用-而且不可预测-你永远无法确定你找到了正确的位置 在代码中添加Applicatio

应用程序中的某个表单显示模型的图形视图。用户可以在其他负载中启动模型转换,这可能需要相当长的时间。这种转换有时在没有任何用户交互的情况下进行,有时需要频繁的用户输入。除非需要用户输入,否则应禁用UI(仅显示进度对话框)

可能的办法:

  • 忽略这个问题,只需将转换代码放在一个过程中并调用它。不好,因为在转换需要一些时间但不需要用户输入的情况下,应用程序似乎挂起
  • 在代码中撒上回调:这很麻烦-你必须在转换代码中加入大量的调用-而且不可预测-你永远无法确定你找到了正确的位置
  • 在代码中添加Application.ProcessMessages:与回调相同的问题。此外,ProcessMessages还存在所有问题
  • 使用线程:这将使我们从2的“突兀和不可预测”部分中解脱出来。三,。然而,由于用户输入所需的“编组”,这是一项大量的工作——调用同步、将任何所需的参数放入定制记录等。调试也是一场噩梦,并且容易出错
  • //编辑:我们当前的解决方案是线程。然而,由于用户的输入,这在a**中是一种痛苦。在很多例程中可能有很多输入代码。这让我觉得线程不是正确的解决方案

    我要让自己难堪,并发布一篇关于我制作的GUI和工作代码的邪恶混合的提纲:

    type
      // Helper type to get the parameters into the Synchronize'd routine:
      PGetSomeUserInputInfo = ^TGetSomeUserInputInfo;
      TGetSomeUserInputInfo = record
        FMyModelForm: TMyModelForm;
        FModel: TMyModel;
        // lots of in- and output parameters
        FResult: Boolean;
      end;
    
    { TMyThread }
    
    function TMyThread.GetSomeUserInput(AMyModelForm: TMyModelForm;
      AModel: TMyModel; (* the same parameters as in TGetSomeUserInputInfo *)): Boolean;
    var
      GSUII: TGetSomeUserInputInfo;
    begin
      GSUII.FMyModelForm := AMyModelForm;
      GSUII.FModel := AModel;
      // Set the input parameters in GSUII
    
      FpCallbackParams := @GSUII; // FpCallbackParams is a Pointer field in TMyThread
      Synchronize(DelegateGetSomeUserInput);
      // Read the output parameters from GSUII
      Result := GSUII.FResult;
    end;
    
    procedure TMyThread.DelegateGetSomeUserInput;
    begin
      with PGetSomeUserInputInfo(FpCallbackParams)^ do
        FResult := FMyModelForm.DoGetSomeUserInput(FModel, (* the params go here *));
    end;
    
    { TMyModelForm }
    
    function TMyModelForm.DoGetSomeUserInput(Sender: TMyModel; (* and here *)): Boolean;
    begin
      // Show the dialog
    end;
    
    function TMyModelForm.GetSomeUserInput(Sender: TMyModel; (* the params again *)): Boolean;
    begin
      // The input can be necessary in different situations - some within a thread, some not.
      if Assigned(FMyThread) then
        Result := FMyThread.GetSomeUserInput(Self, Sender, (* the params *))
      else
        Result := DoGetSomeUserInput(Sender, (* the params *));
    end;
    

    您是否有任何意见?

    通过向队列发送消息异步处理,并让侦听器进行处理。控制器向用户发送一条ACK消息,表示“我们已收到您的处理请求。请稍后再查看结果。”给用户一个邮箱或链接以查看进展情况。

    一定要选择线程选项(甚至在编辑后,说您觉得它很复杂)。在我看来,这是非常糟糕的UI设计(尽管它不是关于外观,而是关于用户如何与应用程序交互)。执行此操作的程序很烦人,因为您不知道任务需要多长时间,何时完成等。改进此方法的唯一方法是在结果上打上生成日期/时间的戳记,但即使这样,您也需要用户记住他们何时开始此过程


    花点时间/精力,让应用程序对您的最终用户有用、信息丰富且不那么令人沮丧。

    TThread是完美且易于使用的

    开发并调试慢函数

    如果准备好了,则将调用放入tthread execute方法中。 使用onThreadTerminate事件查找函数的结尾


    对于用户反馈,请使用syncronize

    要获得最佳解决方案,您必须分析代码,并找到所有位置检查用户是否希望取消长期运行的操作。对于一个简单的过程和一个线程化的解决方案来说,这都是正确的——您希望操作在十分之几秒后完成,以使您的程序对用户做出响应

    现在,我首先要做的是使用以下方法创建一个接口(或抽象基类):

    IModelTransformationGUIAdapter = interface
      function isCanceled: boolean;
      procedure setProgress(AStep: integer; AProgress, AProgressMax: integer);
      procedure getUserInput1(...);
      ....
    end;
    
    并将过程更改为具有此接口或类的参数:

    procedure MyTransformation(AGuiAdapter: IModelTransformationGUIAdapter);
    
    现在,您已经准备好在后台线程或直接在主GUI线程中实现一些东西,一旦添加了更新进度和检查取消请求的代码,就不需要更改转换代码本身。您只能以不同的方式实现接口

    我肯定不会使用辅助线程,特别是如果你想禁用GUI的话。要使用多处理器内核,您总是可以找到转换过程中相对分离的部分,并在它们自己的工作线程中处理它们。这将为您提供比单个工作线程更好的吞吐量,并且使用它很容易实现。只要将它们中的许多并行地启动,就像您有处理器内核一样

    编辑:

    Rob Kennedy的IMO是迄今为止最有洞察力的,因为它不关注实现的细节,而是关注用户的最佳体验。这当然是你的程序应该优化的地方


    如果在转换开始之前无法获取所有信息,或者无法运行转换并在以后修补某些内容,那么您仍然有机会让计算机做更多的工作,以便用户获得更好的体验。我从您的各种评论中看到,转换过程有很多点,其中执行分支取决于用户输入。我想到的一个例子是,用户必须在两个备选方案(如水平或垂直方向)之间进行选择-您可以简单地使用异步调用来启动两个转换,并且有可能在用户选择其备选方案时,两个结果都已计算出来,因此,您可以简单地显示下一个输入对话框。这将更好地利用多核机器。也许是一个跟进的想法。

    我认为,只要您长期运行的转换需要用户交互,您就不会对得到的任何答案感到真正满意。现在让我们回顾一下:为什么需要中断转换并请求更多信息?这些问题真的是你在开始转型之前无法预料的吗?当然,用户对中断也不太满意,对吧?他们不能只是启动改造,然后去喝杯咖啡;他们需要坐下来观看进度条,以防出现错误
    procedure TMyForm .IdleEventHandler(Sender: TObject;
      var Done: Boolean);
    begin
      {Do a small bit of work here}
      Done := false;
    end;
    
    Count := 0;
    Application.OnIdle := IdleEventHandler;
    
    ...
    ...
    
    procedure TMyForm .IdleEventHandler(Sender: TObject;
      var Done: Boolean);
    var
      LocalCount : Integer;
    begin
      LocalCount := 0;
    
      while (Count < MaxCount) and (Count < 10) do
      begin
        {Do a small bit of work here}
        Inc(Count);
        Inc(LocalCount);
      end;
      Done := false;
    end;