Mik*_*ski 14 delphi events sleep timer delphi-xe2
我正在处理的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;
Run Code Online (Sandbox Code Playgroud)
Del*_*ics 16
轮流提出问题:
是(但也是"否" - 见下文).
"正确的方式"根据具体要求和要解决的问题而变化.对此没有普遍的真相,任何告诉你的人都试图向你推销一些东西(用来解释).
在某些情况下,等待事件是适当的延迟机制.在其他情况下没有.
见上文:答案是肯定的.然而,这第二个问题根本就没有,因为它假定有意义的睡眠()是始终和必然永远正确的方式,正如在回答#1以上解释的,并不一定是这样.
Sleep()可能不是在所有场景中编程延迟的最佳或最合适的方法,但有些情况下它是最实用的并且没有明显的缺点.
为什么人们避免睡觉()
Sleep()是一个潜在的问题,因为它是一个无条件的延迟,在特定的时间段过去之前不能中断.替代延迟机制通常实现完全相同的事情,唯一的区别是存在一些替代机制来恢复执行,而不仅仅是时间的流逝.
等待事件延迟直到事件发生(或被销毁)或特定时间段过去.
等待互斥锁会导致延迟,直到获取(或被破坏)互斥锁或经过特定时间段.
等等
换句话说:虽然一些延迟机制是可中断的. 睡眠()不是.但是,如果你得到其他错误的机制,仍然有可能引入重大问题,并且往往以一种更难以识别的方式.
在这种情况下Event.WaitFor()的问题
问题中的原型强调了使用任何暂停代码执行的机制的潜在问题,如果代码的其余部分没有以与特定方法兼容的方式实现:
Form1.Timer1.Enabled := true;
Form1.EventManager.ResetEvent;
Form1.EventManager.WaitFor(INFINITE);
Run Code Online (Sandbox Code Playgroud)
如果在主线程中执行此代码,则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秒后恢复而不会陷入等待的陷阱对于过于复杂(关于手头的问题)技术的粗心大意.
话虽如此,这个原型显然是一个人为的例子.
根据我的经验,Sleep()是最佳解决方案的实际情况很少,尽管它通常是最简单的最不容易出错的.但我永远不会说永远不会.
LU *_* RD 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;
Run Code Online (Sandbox Code Playgroud)
叫它:
var
SE: TSimpleEvent;
...
SE := TSimpleEvent.Create(Nil,False,False,'');
StartActions(SE);
Run Code Online (Sandbox Code Playgroud)
并中止操作(在程序关闭或手动中止的情况下):
SE.SetEvent;
...
FreeAndNil(SE);
Run Code Online (Sandbox Code Playgroud)
这将创建一个匿名线程,其中时间由a驱动TSimpleEvent.当行动就绪时,线程将被自行销毁."全局"事件对象可用于手动或在程序关闭期间中止操作.