【问题标题】:How Can I Minimize Overhead when Processing Messages in a Long Loop在长循环中处理消息时如何最小化开销
【发布时间】:2011-03-25 02:06:50
【问题描述】:

我的 Delphi 程序中有一些很长但很简单的循环,可能循环数百万次并需要几秒钟的时间才能执行。循环内的代码非常快并且已经过优化。只是需要很长时间,因为它做了很多次。

例如:

Screen.Cursor = crHourGlass;
R := FirstRecord;
while R <> nil do begin
  { do something simple with R.Value }
  R := R.NextRecord;
end;
Screen.Cursor := crDefault;

现在我不希望我的程序没有响应,所以我想在循环中添加一个 Application.ProcessMessages。但我也希望添加的语句尽可能减慢我的循环速度。

我正在关注一个链表,所以我什至没有可用的计数变量,如果我想要间隔,就必须添加一个。或者我必须添加一个计时器,但需要尽量减少检查时间。

我应该如何实现它以最大限度地减少增加的开销?


结论:

目前,我正在做类似 APZ28 的回答。

但从长远来看,我应该实现某种线程来处理这个问题。感谢您指出这一点,因为我认为 Application.ProcessMessages 是唯一的方法。

【问题讨论】:

  • 只有当您调用 ProcessMessages 时,计时器才会起作用。

标签: delphi optimization overhead


【解决方案1】:

你能把工作循环放在一个线程中,释放主线程用于 GUI 循环处理。

【讨论】:

  • 我认为您没有太多选择。您将使用线程获得最佳性能,使用 Application.ProcessMessages 实现最简单。选择一个。
  • @lkessler:这不是跳过线程的理由。例如,您可以将循环放入异步调用中(例如使用 AsyncCalls,andy.jgknet.de/blog/?page_id=100)并在 GUI 线程中显示模式进度对话框,可以选择允许取消操作。在主线程中写入布尔值并在工作线程中读取它可以在没有同步的情况下完成,因此根本没有开销。通过互锁增量,工作人员同样可以更新循环计数器,而不会真正影响吞吐量。
  • @Lieven:永远,永远,永远都使用 Application.ProcessMessages 来处理这类事情——除非你竭尽全力阻止它,否则它会在你的应用程序中产生重入问题。 Application.ProcessMessages 不是任何问题的解决方案——它只是问题的原因。而不是使用 ProcessMessages 来模拟线程(它存在于 Delphi 1 中,因为没有其他方法)你应该“正确”地进行线程;用真正的线程)。从短期来看,这似乎需要更多的工作,但从长远来看,Application.ProcessMessages 会花费你更多 - 保证。
  • @Deltics:举个例子,防止重新进入很容易,但如果它让你感觉更好,我更喜欢线程。
  • @Deltics:在这里我不同意。您尝试解决的问题是相同的,只是实现不同,每个都有自己的一组问题。我已经同意线程是要走的路,但解雇 ProcessMessages 是错误的。如果我能为每个使用 ProcessMessages 的应用程序毫无问题地获得一美元(在我的情况下是欧元:),我可能会退休。
【解决方案2】:

把它放在线程下并不是微不足道的,因为它需要锁共享资源(如果有的话)。一个很好的技巧是有一个计数器,在处理# of loop 之后,调用 ProcessMessages

var
  LoopCounter: Integer;

LoopCounter := 0;
R := FirstRecord;
while R <> nil do begin
  Inc(LoopCounter);
  if (LoopCounter >= ???) then
  begin
    LoopCounter := 0;
    Application.ProcessMessages;
  end;

  { do something simple with R.Value }
  R := R.NextRecord;
end;

【讨论】:

  • 如果唯一的其他线程(VCL 线程)在那段时间不接触相同的数据结构,那么将其放入单个辅助线程是完全不重要的。 “共享资源”并不意味着多个线程访问相同的数据,而是意味着这种访问可能是并发的。
  • +1 因为这通常“足够好”。不过线程更好。
【解决方案3】:

我也会投票给一个线程或类似 Anreas 的AsyncCalls。要禁止用户在需要的时间内执行任何不允许的操作,您可以在例程开始时设置一个标志并在结束时将其重置(无论如何您都必须更新 Screen.Cursor)。主线程可以检查此标志并在其 OnUpdate 事件中禁用所有受影响的操作。

【讨论】:

  • 看来mghie的评论和我的回答差不多。
【解决方案4】:

最好的选择是将循环移动到它自己的工作线程中,这样主线程就不会被阻塞,那么你根本不需要调用 ProcessMessages()。

但是,如果必须在主线程中做循环,那么可以使用MsgWaitForMultipleObject()来检测何时调用ProcessMessages(),即:

Screen.Cursor = crHourGlass; 
R := FirstRecord; 
while R <> nil do begin 
  { do something simple with R.Value } 
  if MsgWaitForMultipleObjects(0, nil, False, 0, QS_ALLINPUT) = WAIT_OBJECT_0 then
    Application.ProcessMessages;
  R := R.NextRecord; 
end; 
Screen.Cursor := crDefault; 

也可以使用 PeekMessage():

var Msg: TMsg;

Screen.Cursor = crHourGlass; 
R := FirstRecord; 
while R <> nil do begin 
  { do something simple with R.Value } 
  if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
    Application.ProcessMessages;
  R := R.NextRecord; 
end; 
Screen.Cursor := crDefault; 

也可以使用 GetQueueStatus():

Screen.Cursor = crHourGlass; 
R := FirstRecord; 
while R <> nil do begin 
  { do something simple with R.Value } 
  if GetQueueStatus(QS_ALLINPUT) <> 0 then
    Application.ProcessMessages;
  R := R.NextRecord; 
end; 
Screen.Cursor := crDefault; 

【讨论】:

    【解决方案5】:

    要决定的一个问题是,您的应用程序是否可以在您得到循环计算的答案之前继续运行。如果它不能,那么应用程序“响应”就没有多大意义了。如果您尝试更新进度条或其他内容,则可以每隔一定次数的迭代在包含进度条的控件上调用 .Repaint 以使进度条重新绘制。

    如果应用程序至少可以继续运行一段时间,那么将代码放入线程中是个好主意。

    无论如何,将循环代码放在线程中可能是合理的,特别是如果您想做一些可能中止处理的事情。如果您以前从未使用过线程,则有一点学习曲线,但是对于像您描述的简单循环,网络上有很多示例。

    【讨论】:

    • 我完全不同意。应用程序必须始终响应。如果需要一段时间,用户可能想要移动它或最小化它。如果卡在一个循环中,他就不能。
    猜你喜欢
    • 2017-12-16
    • 2021-04-03
    • 1970-01-01
    • 1970-01-01
    • 2018-03-15
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 2015-06-06
    相关资源
    最近更新 更多