Delphi : Sleep without freeze and processmessages
Asked Answered
P

4

9

I need a way to pause the execution of a function for some seconds. I know i can use the sleep method to do it, but this method 'freezes' the application while its execution. I also know i can use something like the code below to avoid freezing :

// sleeps for 5 seconds without freezing 
for i := 1 to 5 do
    begin
    sleep(1000);
    application.processmessages;
    end;

There are two problems of this approach : one is the fact the freezing still occurs each one second and the second problem is the calling to 'application.processmessages' each second. My app is CPU intensive and each processmessages call do a lot of unnecessary work that uses unnecessary CPU power ; i just want to pause the workflow, nothing more.

What i really need would be a way to pause the execution just like a TTimer, in the example below :

   // sleeps for 5 seconds
   mytimer.interval := 5000;
   mytimer.enabled := true;
   // wait the timer executes
   // then continue the flow
   // running myfunction
   myfunction;

The problem of this approach is 'myfunction' won't wait the for mytimer, it will run right after the mytimer is enabled.

Is there another approach to achieve a pause like i want ?

Thanks in advance.

Polivy answered 24/6, 2015 at 12:22 Comment(5)
So my best option is to use a lot of TTimers with the pauses i want ?Polivy
I am a bit baffled by the phrase "each processmessages call do a lot of unnecessary work". Isn't ProcessMessages similar to the thing that is done when the application is not freezing? So if not freezing is doing unnecessary work this should be an area to fix in the first place.Williawilliam
I've done things like this in various apps for various reasons. The big thing is that it depends on why you need to pause it - that will direct what method you can use to handle it.November
@UweRaabe: not quite. When you let the message loop do its work normally, the main thread is put into an efficient sleep during times when there are no messages to process. By calling ProcessMessages() in a loop, the main thread is not sleeping anymore, and there is overhead in polling the message queue for messages even if there are no messages waiting to be processed.Prothalamium
I need a way to pause the execution of a function for some seconds -- Why? Should we guess it by means of the accepted answer? Do us (and future visitors) a favour and tell a bit more about your specific problem. Thanks :)Groundsill
P
19

As David stated, the best option is to move the work into a separate thread and stop blocking the main thread altogether. But, if you must block the main thread, then at the very least you should only call ProcessMessages() when there really are messages waiting to be processed, and let the thread sleep the rest of the time. You can use MsgWaitForMultipleObjects() to handle that, eg:

var
  Start, Elapsed: DWORD;

// sleep for 5 seconds without freezing 
Start := GetTickCount;
Elapsed := 0;
repeat
  // (WAIT_OBJECT_0+nCount) is returned when a message is in the queue.
  // WAIT_TIMEOUT is returned when the timeout elapses.
  if MsgWaitForMultipleObjects(0, Pointer(nil)^, FALSE, 5000-Elapsed, QS_ALLINPUT) <> WAIT_OBJECT_0 then Break;
  Application.ProcessMessages;
  Elapsed := GetTickCount - Start;
until Elapsed >= 5000;

Alternatively:

var
  Ret: DWORD;
  WaitTime: TLargeInteger;
  Timer: THandle;

// sleep for 5 seconds without freezing 
Timer := CreateWaitableTimer(nil, TRUE, nil);
WaitTime := -50000000; // 5 seconds
SetWaitableTimer(Timer, WaitTime, 0, nil, nil, FALSE);
repeat
  // (WAIT_OBJECT_0+0) is returned when the timer is signaled.
  // (WAIT_OBJECT_0+1) is returned when a message is in the queue.
  Ret := MsgWaitForMultipleObjects(1, Timer, FALSE, INFINITE, QS_ALLINPUT);
  if Ret <> (WAIT_OBJECT_0+1) then Break;
  Application.ProcessMessages;
until False;
if Ret <> WAIT_OBJECT_0 then
  CancelWaitableTimer(Timer);
CloseHandle(Timer);
Prothalamium answered 24/6, 2015 at 18:22 Comment(0)
R
8

Move the task that needs to be paused into a separate thread so that it does not interfere with the UI.

Riccio answered 24/6, 2015 at 12:28 Comment(5)
So if i have a lot of tasks to be paused, i need one thread for each ? No way to use sleep as i want ?Polivy
Call Sleep in the UI thread and you'll kill the UI. Ergo, don't call Sleep in the UI thread. Ergo, if you want to call Sleep do it in another thread. As for how to solve your actual problem in the best way, we can't see that problem fully.Riccio
Beyond all of that you need to appreciate that once a thread pauses it cannot do anything else. To pause a function part way through, do other stuff, and then resume, without threads, probably means making a state machine. Not trivial.Riccio
Check the solution of @remy-lebeau, it does exactly what i was looking for.Polivy
I just wanted to pause without freeze the UI, and without calling processmessages unnecessary. I agree the best option is to move the task to another thread, but for while i can't do it, would be a lot of workk. Remy solution solved the issue for now, so i have time to edit the code and do the right thing.Polivy
F
1

It is rather doubtful, that Application.ProcessMessages will really consumpt too much processor time. You can try to store the moment of time when you start waiting and then begin a repeat Application.ProcessMessages until...; circle checking the time span between the stored and current time.

Fagen answered 24/6, 2015 at 16:32 Comment(1)
That's a busy loop which will peg a processor. Not to mention ProcessMessages being evil.Riccio
S
0

If you have not a problem with using a timer, you can do this:

(ouside the timer-event:)
mytimer.interval := 5000;
mytimer.tag:=0;
mytimer.enabled := true;

(inside the timer-event:)
mytimer.tag:=mytimer.tag+1;
if mytimer.tag=2 then begin
  mytimer.enabled:=false;
  myfunction;
end;
Simpleminded answered 8/7, 2015 at 19:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.