我正在处理的Delphi应用程序必须延迟一秒,有时两秒钟。我想使用最佳实践来编制此延迟。在stackoverflow上阅读关于Delphi的Sleep()方法的条目时,我发现了这两条评论:
我遵循这句格言:“如果你觉得有必要使用Sleep(),那你做错了。” - Nick Hodges 12年12月12日1:36
@nick确实。我的等价物是“没有问题,睡眠是解决方案。” - David Heffernan 12年12月12日8:04
为了回应这个避免调用Sleep()的建议,以及我对使用Delphi的TTimer和TEvent类的理解,我编写了以下原型。我的问题是:
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
public
EventManager: TEvent;
end;
TDoSomething = class(TThread)
public
procedure Execute; override;
procedure Delay;
end;
var
Form1: TForm1;
Something: TDoSomething;
implementation
{$R *.dfm}
procedure TDoSomething.Execute;
var
i: integer;
begin
FreeOnTerminate := true;
Form1.Timer1.Interval := 2000; // 2 second interval for a 2 second delay
Form1.EventManager := TEvent.Create;
for i := 1 to 10 do
begin
Delay;
writeln(TimeToStr(GetTime));
end;
FreeAndNil(Form1.EventManager);
end;
procedure TDoSomething.Delay;
begin
// Use a TTimer in concert with an instance of TEvent to implement a delay.
Form1.Timer1.Enabled := true;
Form1.EventManager.ResetEvent;
Form1.EventManager.WaitFor(INFINITE);
Form1.Timer1.Enabled := false;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Something := TDoSomething.Create;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
// Time is up. End the delay.
EventManager.SetEvent;
end;
答案 0 :(得分:14)
轮流提问:
是(但也是#34;不" - 见下文)。
正确的方法'根据具体要求和正在解决的问题而变化。此处没有普遍真相,任何告诉你的人都试图向你推销一些东西(用来解释)。
在某些情况下,等待事件是适当的延迟机制。在其他情况下没有。
见上文:答案是是。然而,第二个问题根本没有意义,因为它假设 Sleep()总是 永远不是正确的方式,正如在#的答案中所解释的那样上面的1,不一定是这样。
睡眠()可能不是编制所有方案延迟的最佳或最合适的方法,但有些情况下它是最实用的,没有显着的缺点。
为什么人们会避免睡眠()
睡眠()是一个潜在的问题,因为它是一个无条件的延迟,在特定的时间段过去之前不能中断。替代延迟机制通常实现完全相同的事情,唯一的区别是存在一些替代机制来恢复执行,而不仅仅是时间的流逝。
等待事件延迟,直到事件发生(或被销毁)或特定时间段过去。
等待互斥锁导致延迟,直到获取(或被破坏)互斥锁或特定时间段过去。
等
换句话说:虽然一些延迟机制是可中断的。 睡眠()不是。但是,如果你让其他机制出错,那么仍有可能引入重大问题,并且往往会以一种更难以识别的方式。
在这种情况下使用Event.WaitFor()的问题
问题中的原型强调了使用任何机制暂停代码执行的潜在问题,如果代码的其余部分未以与兼容的方式实现方法:
Form1.Timer1.Enabled := true;
Form1.EventManager.ResetEvent;
Form1.EventManager.WaitFor(INFINITE);
如果在主线程中执行此代码,则 Timer1 将永远不会发生。
问题中的原型在一个线程中执行这个,所以这个特殊的问题并没有出现,但是由于原型确实因为这个线程的介入而引入了一个不同的问题,因此值得探索。
通过在事件上的 WaitFor()上指定 INFINITE 等待超时,可以暂停执行该线程,直到该事件发生。 TTimer 组件使用基于Windows消息的计时器机制,其中在计时器结束时向您的消息队列提供 WM_TIMER 消息。要发生 WM_TIMER 消息,您的应用程序必须处理其消息队列。
也可以创建Windows计时器,它将在另一个线程上提供回调,这可能是一种更合适的方法(在这种情况下是人为的)。但是,这不是VCL TTimer 组件提供的功能(至少从XE4开始,我注意到您使用的是XE2)。
问题#1
如上所述, WM_TIMER 消息依赖于您的应用程序处理其消息队列。您已指定了2秒计时器,但如果您的应用程序进程忙于执行其他工作,则处理该消息的时间可能会超过2秒。
值得一提的是 Sleep()也会受到一些不准确的影响 - 它确保线程在指定的时间内暂停至少,它确实不确保指定的延迟。
问题#2
该原型设计了一种机制,使用计时器和事件延迟2秒,以实现几乎完全相同的结果,只需通过简单调用 Sleep()即可实现。
这与简单的 Sleep()调用之间的唯一区别是,如果正在等待的事件被破坏,您的线程也将恢复。
然而,在真实情况下,一些进一步的处理遵循延迟,如果处理不当,这本身就是一个潜在的重大问题。在原型中,这种可能性根本不能满足。即使在这种简单的情况下,如果事件被破坏,那么线程也会尝试禁用 Timer1 。当线程尝试禁用该计时器时,可能会在线程中发生访问冲突。
警告开发者
小心地避免使用 Sleep()无法替代正确理解所有线程同步机制(其中延迟只是一个)以及操作系统本身的工作方式,以便可以根据需要部署正确的技术。
事实上,就你的原型而言, Sleep()可以说是更好的"解决方案(如果可靠性是关键指标),因为该技术的简单性确保您的代码将在2秒后恢复,而不会陷入等待过度复杂(对于手头的问题)技术的不谨慎的陷阱。 p>
话虽如此,这个原型显然是一个人为的例子。
根据我的经验,很少实际情况中 Sleep()是最佳解决方案,尽管它通常是最简单的最不容易出错的。但我永远不会说永远不会。
答案 1 :(得分:11)
方案: 你想在它们之间有一定的延迟执行一些连续的动作。
这是编程延迟的正确方法吗?
我想说有更好的方法,见下文。
如果答案是肯定的,那为什么这比调用Sleep()更好呢?
睡在主线程中是一个坏主意:记住,windows范例是事件驱动的,即根据动作执行任务,然后让系统处理接下来发生的事情。在线程中休眠也很糟糕,因为您可以停止来自系统的重要消息(在关闭等情况下)。
您的选择是:
从主线程中的计时器处理您的操作,就像状态机一样。跟踪状态,并在计时器事件触发时执行代表此特定状态的操作。这适用于在每个计时器事件的短时间内完成的代码。
将操作行放在一个帖子中。使用事件超时作为计时器,以避免使用睡眠调用冻结线程。这些类型的操作通常是I / O绑定的,您可以使用内置超时调用函数。在这些情况下,超时数量是自然延迟。这就是我所有通信库的构建方式。
后一种选择的例子:
procedure StartActions(const ShutdownEvent: TSimpleEvent);
begin
TThread.CreateAnonymousThread(
procedure
var
waitResult: TWaitResult;
i: Integer;
begin
i := 0;
repeat
if not Assigned(ShutdownEvent) then
break;
waitResult := ShutdownEvent.WaitFor(2000);
if (waitResult = wrTimeOut) then
begin
// Do your stuff
// case i of
// 0: ;
// 1: ;
// end;
Inc(i);
if (i = 10) then
break;
end
else
break; // Abort actions if process shutdown
until Application.Terminated;
end
).Start;
end;
称之为:
var
SE: TSimpleEvent;
...
SE := TSimpleEvent.Create(Nil,False,False,'');
StartActions(SE);
中止操作(在程序关闭或手动中止的情况下):
SE.SetEvent;
...
FreeAndNil(SE);
这将创建一个匿名线程,其时间由TSimpleEvent
驱动。当行动就绪时,线程将被自行销毁。 "全球"事件对象可用于手动或在程序关闭期间中止操作。
答案 2 :(得分:-1)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Memo1: TMemo;
Timer1: TTimer;
RichEdit1: TRichEdit;
Button1: TButton;
CheckBox1: TCheckBox;
procedure Delay(TickTime : Integer);
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Past: longint;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
delay(180000);
beep;
end;
procedure TForm1.Delay(TickTime: Integer);
begin
Past := GetTickCount;
repeat
application.ProcessMessages;
Until (GetTickCount - Past) >= longint(TickTime);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if checkbox1.Checked=true then Past:=0;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
end;
end.
答案 3 :(得分:-2)
这是一个过程,它在调用ProcessMessages时要等待一段时间(这是为了使系统保持响应)。
procedure Delay(TickTime : Integer);
var
Past: longint;
begin
Past := GetTickCount;
repeat
application.ProcessMessages;
Until (GetTickCount - Past) >= longint(TickTime);
end;