Delphi:无冻结休眠和处理消息

9

我需要一种方法来暂停函数的执行几秒钟。我知道可以使用sleep方法来实现,但是该方法在执行时会“冻结”应用程序。我还知道可以使用以下代码来避免冻结:

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

这种方法存在两个问题:一是仍然每秒钟发生冻结,二是每秒钟调用“application.processmessages”。我的应用程序需要大量CPU资源,而每个processmessages调用都会执行很多不必要的工作,浪费CPU资源;我只想暂停工作流程,不需要其他操作。我真正需要的是一种像TTimer一样暂停执行的方法,如下面的示例所示:
   // sleeps for 5 seconds
   mytimer.interval := 5000;
   mytimer.enabled := true;
   // wait the timer executes
   // then continue the flow
   // running myfunction
   myfunction;

这种方法的问题是'myfunction'不会等待mytimer,它会在mytimer启用后立即运行。是否有另一种方法可以实现我想要的暂停?提前感谢您。

那么,我的最佳选择是使用许多带有我想要的暂停的 TTimer 吗? - delphirules
我对“每个processmessages调用都会做很多不必要的工作”这句话有些困惑。ProcessMessages不是类似应用程序没有冻结时所做的事情吗?因此,如果“没有冻结”正在执行“不必要的工作”,这应该是首先需要解决的问题。 - Uwe Raabe
1
我为各种应用程序出于各种原因做过类似的事情。最重要的是,这取决于您需要暂停它的原因-这将指导您可以使用哪种方法来处理它。 - MikeD
@UweRaabe:并不完全正确。当您让消息循环正常工作时,主线程在没有要处理的消息时会进入有效的睡眠状态。通过在循环中调用ProcessMessages(),主线程不再处于睡眠状态,并且即使没有等待处理的消息,轮询消息队列以获取消息也会产生开销。 - Remy Lebeau
1
我需要一种方法来暂停函数执行几秒钟。--为什么?我们应该通过接受的答案猜测吗?请做我们(和未来的访问者)一个忙,多谈一些关于您具体问题的信息。谢谢 :) - Wolf
4个回答

19

正如David所说,最佳选项是将工作移至单独的线程并停止完全阻塞主线程。但如果必须阻塞主线程,则至少应在确实有待处理消息时才调用ProcessMessages(),而让线程其余时间处于休眠状态。 您可以使用MsgWaitForMultipleObjects()来处理,例如:

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;

另外:

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);

8
将需要暂停的任务移动到单独的线程中,以免干扰用户界面。

那如果我有很多任务需要暂停,我需要为每个任务创建一个线程吗?无法按我的意愿使用sleep函数吗? - delphirules
2
在UI线程中调用Sleep会导致UI停止响应。因此,不要在UI线程中调用Sleep。如果你想调用Sleep,请在另一个线程中执行。至于如何以最佳方式解决实际问题,我们无法完全了解该问题。 - David Heffernan
1
除此之外,您需要明白一旦线程暂停,它就不能再做其他事情了。如果想要在函数执行过程中暂停,做其他事情,然后恢复执行,而不使用线程,可能意味着需要制作一个状态机。这并不是一件简单的事情。 - David Heffernan
检查@remy-lebeau的解决方案,它正好符合我的要求。 - delphirules
我只是想暂停而不冻结用户界面,也不需要调用processmessages。我同意最好的选择是将任务移动到另一个线程,但目前我无法这样做,这将是很多工作。Remy的解决方案暂时解决了问题,所以我有时间编辑代码并做正确的事情。 - delphirules

1

很难确定Application.ProcessMessages会真正消耗太多处理器时间。你可以尝试存储等待开始的时间,并开始重复执行Application.ProcessMessages直到......;循环检查存储和当前时间之间的时间跨度。


2
那是一个繁忙的循环,会占用处理器。更不用说 ProcessMessages 是有害的了。 - David Heffernan

0
如果您不介意使用计时器,可以这样做:
(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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接