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
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:
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;
Taking your questions in turn:
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.
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.