Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to program a delay in Delphi?

A Delphi application that I'm working on must delay for one, or sometimes two, second(s). I want to program this delay using the best practices. In reading entries about Delphi's Sleep() method on stackoverflow, I found these two comments:

I live by this maxim: "If you feel the need to use Sleep(), you are doing it wrong." – Nick Hodges Mar 12 '12 at 1:36

@nick Indeed. My equivalent is "There are no problems for which Sleep is the solution." – David Heffernan Mar 12 '12 at 8:04

comments about Sleep()

In response to this advice to avoid calling Sleep(), along with my understanding about using Delphi's TTimer and TEvent classes, I have programmed the following prototype. My questions are:

  1. Is this a proper way to program a delay?
  2. If the answer is yes, then why is this better than a call to Sleep()?

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;
like image 713
Mike Jablonski Avatar asked Feb 22 '15 22:02

Mike Jablonski


3 Answers

Taking your questions in turn:

  1. Is this a proper way to program a delay?

Yes (but also "no" - see below).

The 'proper way' varies according to the specific requirements and the problem being solved. There is no Universal Truth on this and anyone telling you otherwise is trying to sell you something (to paraphrase).

In some cases waiting on an event is the proper delay mechanism. In other cases not.

  1. If the answer is yes, then why is this better than a call to Sleep()?

See above: The answer is yes. However, this second question simply does not make sense since it assumes that Sleep() is always and by necessity never the proper way which, as is explained in the answer to #1 above, is not necessarily the case.

Sleep() may not be the best or most appropriate way to program a delay in all scenarios, but there are scenarios where it is the most practical and has no significant drawbacks.

Why People Avoid Sleep()ing

Sleep() is a potential problem precisely because it is an unconditional delay that cannot be interrupted until a specific time period has elapsed. Alternative delay mechanisms typically achieve precisely the same thing with the only difference being that there exists some alternative mechanism to resume execution, other than the mere passage of time.

Waiting for an event delays until the event occurs (or is destroyed) or a specific period of time has passed.

Waiting for an mutex causes a delay until the mutex is acquired (or is destroyed) or a specific period of time has passed.

etc.

In other words: Whilst some delay mechanisms are interruptible. Sleep() is not. But if you get the other mechanisms wrong there is still the potential to introduce significant problems and often in a way that can be far more difficult to identify.

Problems With Event.WaitFor() In This Case

The prototype in the question highlights a potential problem of using any mechanism that suspends execution of your code if the rest of that code is not implemented in a way that is compatible with that particular approach:

 Form1.Timer1.Enabled := true;
 Form1.EventManager.ResetEvent;
 Form1.EventManager.WaitFor(INFINITE);

If this code is executed in the main thread then Timer1 will never happen.

The prototype in the question executes this in a thread, so this particular problem doesn't arise, but it is worth exploring the potential since the prototype does introduce a different problem as a result of the involvement of this thread.

By specifying an INFINITE wait timeout on your WaitFor() on the event, you suspend execution of the thread until that event occurs. The TTimer component uses the windows message based timer mechanism, in which a WM_TIMER message is supplied to your message queue when the timer has elapsed. For the WM_TIMER message to occur, your application must be processing its message queue.

Windows timers can also be created which will provide a callback on another thread, which might be a more appropriate approach in this (admittedly artificial) case. However this is not a capability offered by the VCL TTimer component (as of XE4 at least, and I note you are using XE2).

Problem #1

As noted above, WM_TIMER messages rely on your application processing its message queue. You have specified a 2 second timer but if your application process is busy doing other work it could potentially take far longer than 2 seconds for that message to be processed.

Worth mentioning here is that Sleep() is also subject to some inaccuracy - it ensures that a thread is suspended for at least the specified period of time, it does not guarantee exactly the specified delay.

Problem #2

The prototype contrives a mechanism to delay for 2 seconds using a timer and an event to achieve almost exactly the same result that could have been achieved with a simple call to Sleep().

The only difference between this and a simple Sleep() call is that your thread will also resume if the event it is waiting for is destroyed.

However, in a real-world situation where some further processing follows the delay, this is itself a potentially significant problem if not correctly handled. In the prototype this eventuality is not catered for at all. Even in this simple case it is most likely that if the event has been destroyed then so too has the Timer1 that the thread attempts to disable. An Access Violation is likely to occur in the thread as a result when it attempts to disable that timer.

Caveat Developor

Dogmatically avoiding the use of Sleep() is no substitute for properly understanding all thread synchronization mechanisms (of which delays are just one) and the way in which the operating system itself works, in order that the correct technique may be deployed as each occasion demands.

In fact, in the case of your prototype, Sleep() provides arguably the "better" solution (if reliability is the key metric) since the simplicity of that technique ensures that your code will resume after 2 seconds without falling into the pitfalls that await the unwary with over-complicated (with respect to the problem at hand) techniques.

Having said that, this prototype is clearly a contrived example.

In my experience there are very few practical situations where Sleep() is the optimal solution, though it is often the simplest least error prone. But I would never say never.

like image 185
Deltics Avatar answered Nov 19 '22 00:11

Deltics


Scenario: You want to perform some consecutive actions with a certain delay between them.

Is this a proper way to program a delay?

I would say there are better ways, see below.

If the answer is yes, then why is this better than a call to Sleep()?

Sleeping in the main thread is a bad idea: remember, the windows paradigm is event driven, i.e do your task based on an action and then let the system handle what happens next. Sleeping in a thread is also bad, since you can stall important messages from the system (in case of shutdown, etc).

Your options are:

  • Handle your actions from a timer in the main thread like a state machine. Keep track of the state and just execute the action which represents this particular state when the timer event fires. This works for code that finishes in a short time for each timer event.

  • Put the line of actions in a thread. Use an event timeout as a timer to avoid freezing the thread with sleep calls. Often these types of actions are I/O bound, where you call functions with built-in timeout. In those cases the timeout number serves as a natural delay. This is how all my communication libraries are built.

Example of the latter alternative:

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;

Call it:

var
  SE: TSimpleEvent;
...
SE := TSimpleEvent.Create(Nil,False,False,'');
StartActions(SE);

And to abort the actions (in case of program shutdown or manual abort):

SE.SetEvent;
...
FreeAndNil(SE);

This will create an anonymous thread, where the timing is driven by a TSimpleEvent. When the line of actions are ready, the thread will be self destroyed. The "global" event object can be used to abort the actions manually or during program shutdown.

like image 39
LU RD Avatar answered Nov 18 '22 23:11

LU RD


Here's a cleaner way to write a sleep utility that doesn't block the main thread. Use it like this:

procedure SomeFunction(a:Integer);
begin
  var b:= 2;

  TSleep.Create(
    5000, //milliseconds of sleep
    procedure() begin
      //this "anonymous procedure" runs after 5 seconds
      //with full access to variables and input arguments 
      //like a & b.

    end
  ); //TSleep frees itself via the FreeOnTerminate setting
end;

Add the TSleep utility to the interface section...

type
TCallback = reference to procedure();

TSleep = class(TThread)
protected
  procedure Execute; override;
private
  pMs: Integer;
  pCallback: TCallback;
public
  constructor Create(const aMs:Integer; aCallback:TCallback); virtual;
end;

...and the implementation section.

{ TSleep }
constructor TSleep.Create(const aMs: Integer; aCallback: TCallback);
begin
  inherited Create(false);//false means the Execute function runs immediately
  FreeOnTerminate:= true;
  NameThreadForDebugging('Sleep');

  //save the input arguments for use by the new thread
  pMs:= aMs;
  pCallback:= aCallback;
end;

procedure TSleep.Execute;
begin
  //this runs in separate thread

  //wait
  var pt:= nil;//pt must be a variable
  MsgWaitForMultipleObjects(0, pt, false, pMs, 0);

  //callback
  Synchronize(
    procedure begin
      //this runs in main thread
      pCallback();
    end
  );
end;
like image 42
CaptureWiz Avatar answered Nov 18 '22 22:11

CaptureWiz