4

我的 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 是唯一的方法。

4

5 回答 5

8

您可以将工作循环放在一个线程中,从而释放主线程以进行 GUI 循环处理。

于 2010-08-04T05:33:35.583 回答
4

把它放在线程下并不是微不足道的,因为它需要锁共享资源(如果有的话)。一个好技巧是有一个计数器,在处理 # 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;
于 2010-08-04T14:58:18.907 回答
2

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

于 2010-08-04T06:39:59.250 回答
2

最好的选择是将循环移动到它自己的工作线程中,这样主线程就不会被阻塞,那么你根本不需要调用 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; 
于 2010-08-04T19:41:26.250 回答
1

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

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

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

于 2010-08-04T06:08:10.403 回答