18

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;
Community
  • 1
  • 1
Mike Jablonski
  • 1,703
  • 6
  • 27
  • 41
  • Please explain **what** has to be delayed. - btw "best way" smells "primarily opinion based" – Sir Rufo Feb 22 '15 at 22:42
  • The application is interfaced with an external device. The application often is told, by the external device, to standby and wait for for one or more seconds, before it has data available for the application. – Mike Jablonski Feb 22 '15 at 22:56
  • 1
    Is it a **push** or **pull**? Does the external device notify the application, when data is ready (push), or do you have to ask again and again until data is available (pull)? Anyway you should always use an event for waiting. Infinite timeout with push or some milliseconds with pull – Sir Rufo Feb 22 '15 at 23:06
  • Both. My application, however, never asks "again and again" for data. The application is told, by the external device, that data will be ready in x seconds. In some cases, the external device will signal that the data are ready sooner. Many times it will not. The application must then wait for at least x seconds and then request the data. There is not requirement to get the data after exactly x seconds. – Mike Jablonski Feb 22 '15 at 23:12
  • 1
    Unnecessarily complicated, you can remove the timer altogether and wait on the event for 2000ms instead of infinite. Let it timeout on itself instead of poking it after a timer interval. Then it comes down to if `WaitForMultipleObjectsEx` is better then `Sleep`. My personal view, I don't find anything wrong with Sleep here. – Sertac Akyuz Feb 23 '15 at 00:28
  • I understand the confusion however, +1.. – Sertac Akyuz Feb 23 '15 at 00:36
  • Why is a simple TTimer not good enough? When you are told to wait, set and start the timer. Then, in the OnTimer event resume what you were doing. – Tom Brunberg Feb 23 '15 at 08:49
  • 5
    I use sleep all the time in industrial automation - always on a background thread, but for exactly the reasons above. Sometimes you have to wait for things in the real world that either don't or won't communicate their readiness to you but that nevertheless become ready in a very regular and timely manner. It's not necessarily elegant, but it is logical, understandable, and it works. Not "the best" solution in an idealist fantasy, but "the best" solution in a pragmatic realist's world. – J... Feb 23 '15 at 09:29

5 Answers5

19

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.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • TDoSomething.Delay is called from a thread. This means that the thread is put to sleep, while the main thread still can process messages. – LU RD Feb 23 '15 at 06:08
  • 1
    Sometimes we say never to provide emphasis. As a means to encourage people to stop and listen. In this question's scenario, using `Sleep` will sometimes mean a delay when shutdown. That may be acceptable. It is certainly avoidable. – David Heffernan Feb 23 '15 at 07:11
  • @David: The subtleties of *why* someone says "never" are often lost on the person listening. More accurately I would say that "never" is resorted to when the more complete and accurate advice is considered too complex for lesser mortals to understand. – Deltics Feb 23 '15 at 18:40
  • @LU RD - Ah yes, I missed that TDoSomething was a TThread derived class. Thanks for the heads up. – Deltics Feb 23 '15 at 18:41
  • 1
    Or the space available for the comment precludes a full explanation – David Heffernan Feb 23 '15 at 18:43
  • @David, "There are very few occasions when Sleep() is an appropriate solution and you should be careful to understand the implications, the possible alternatives and employ the right one for your particular needs. It is a complex subject that cannot be covered in a comment and you must do some additional research." Which seems to make the necessary point without resorting to "never". It seems to me that certain people use "never" and really do mean "never" and then have to retcon a justification for doing so when they can't actually substantiate that position. Some people. Not all. ;) – Deltics Feb 24 '15 at 01:36
  • Still, sleep is no good here, so perhaps it's easiest for me to stick to my guns. – David Heffernan Feb 24 '15 at 04:30
12

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.

LU RD
  • 34,438
  • 5
  • 88
  • 296
  • This is a really interesting approach +1 – SilverWarior Feb 23 '15 at 07:22
  • The statement *"use an event timeout as a timer to avoid freezing the thread with sleep calls"* is right, the implementation is not. You're doing just that. And due to *anonymity* there's no way to terminate the thread *from outside* (even if it were, you might be waiting for 2 seconds on the `WaitFor` line in the worst case). – TLama Feb 23 '15 at 09:11
  • @TLama, Yes you are correct. To shut down the anonymous thread in an orderly way, one must expose the signal event. Updated answer. – LU RD Feb 23 '15 at 10:14
  • Is it safe to free the event directly after signaling from the main thread? Doesn't that introduce a race condition? – Jens Mühlenhoff Feb 23 '15 at 11:00
  • @LURD Thanks for addressing my concern with that update. I'll remove my comments. – David Heffernan Feb 23 '15 at 11:57
  • @LU RD, Thank you for answering my question. Your answer is helpful and interesting. I will experiment with your alternative. – Mike Jablonski Feb 24 '15 at 16:13
1

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;
CaptureWiz
  • 1,685
  • 1
  • 15
  • 15
0
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.
novice90
  • 11
  • 1
  • Hey @novice90, welcome to SO. Please have a look at following link on [how to write a good answer](https://stackoverflow.com/help/answering). It helps if you give some additional explanation instead of just pasting code. – TheWhiteRabbit May 09 '19 at 08:34
  • Please add some explanation. – Adrita Sharma May 09 '19 at 08:41
-1

This is a procedure to wait a certain period of time while it is calling ProcessMessages (this is to keep the system responsive).

procedure Delay(TickTime : Integer);
 var
 Past: longint;
 begin
 Past := GetTickCount;
 repeat
 application.ProcessMessages;
 Until (GetTickCount - Past) >= longint(TickTime);
end;
Oli
  • 103
  • 8