문제

I've got some long but simple loops in my Delphi program that may loop millions of times and take some seconds to execute. The code inside of the loop is very fast and has been optimized. It just takes long because it is done so many times.

e.g.:

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

Now I don't want my program to be non-responsive, so I want to add an Application.ProcessMessages inside the loop. But I also want the added statements to slow down my loop as little as possible.

I am following a linked list, so I don't even have a counting variable available and would have to add one if I wanted intervals. Or I'd have to add a timer, but need to minimize the time checking.

How should I implement this to minimize the overhead that's added?


Conclusion:

For now, I'm doing something like APZ28's answer.

But it looks like long term I should implement some sort of threading to handle this. Thanks for pointing this out, because I thought that Application.ProcessMessages was the only way to do it.

도움이 되었습니까?

해결책

Can you put the work loop in a thread, freeing up the main thread for the GUI loop processing.

다른 팁

Put it under thread is not trival since it requires lock share resource if any. A good trick is having a counter, after processing # of loop, call 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;

I would also vote for a thread or something like Anreas' AsyncCalls. To prohibit the user doing any non-allowed actions during the time needed, you can set a flag when the routine starts and reset it when it ends (you have to update Screen.Cursor anyway). The main thread can check this flag and disable all affected actions in their OnUpdate event.

The best option is to move the loop into its own worker thread so the main thread is not blocked, then you do not need to call ProcessMessages() at all.

However, if you must do the loop in the main thread, then you can use MsgWaitForMultipleObject() to detect when to call ProcessMessages(), ie:

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; 

Alternatively with 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; 

Alternatively with 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; 

One question to decide is whether or not your application can continue before you have the answer to the whatever the loop is calculating. If it cannot, then there is not much point in the application being "responsive". If you are trying to update a progress bar or something, you can call .Repaint on the control containing the progress bar every certain number of iterations to cause the progress bar to repaint.

If the application can continue, at least for a while, then putting the code in a thread is a good idea.

Putting the looping code in a thread is probably reasonable anyhow, especially if you want to do things like possibly abort the processing. If you have never used threads before, there is a bit of a learning curve, but for a simple loop like you describe there are lots of examples on the web.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top