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