编写Delphi延迟的最佳方法是什么?

时间:2015-02-22 22:33:30

标签: delphi events timer delphi-xe2 sleep

我正在处理的Delphi应用程序必须延迟一秒,有时两秒钟。我想使用最佳实践来编制此延迟。在stackoverflow上阅读关于Delphi的Sleep()方法的条目时,我发现了这两条评论:

  

我遵循这句格言:“如果你觉得有必要使用Sleep(),那你做错了。” - Nick Hodges 12年12月12日1:36

     

@nick确实。我的等价物是“没有问题,睡眠是解决方案。” - David Heffernan 12年12月12日8:04

comments about Sleep()

为了回应这个避免调用Sleep()的建议,以及我对使用Delphi的TTimer和TEvent类的理解,我编写了以下原型。我的问题是:

  1. 这是编程延迟的正确方法吗?
  2. 如果答案是肯定的,那么为什么这比调用Sleep()更好呢?

  3. 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;
    

4 个答案:

答案 0 :(得分:14)

轮流提问:

  1. 这是编程延迟的正确方法吗?
  2. 是(但也是#34;不" - 见下文)。

    正确的方法'根据具体要求和正在解决的问题而变化。此处没有普遍真相,任何告诉你的人都试图向你推销一些东西(用来解释)。

    在某些情况下,等待事件是适当的延迟机制。在其他情况下没有。

    1. 如果答案是肯定的,那么为什么这比调用Sleep()更好呢?
    2. 见上文:答案是。然而,第二个问题根本没有意义,因为它假设 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;