Каков наилучший способ программирования задержки в Delphi?

приложение Delphi, над которым я работаю, должно задерживаться на одну, а иногда и две секунды. Я хочу запрограммировать эту задержку, используя лучшие практики. При чтении записей о методе Sleep () Delphi в stackoverflow я нашел эти два комментария:

Я живу по этой максиме: "если вы чувствуете необходимость использовать Sleep(), вы делаете это неправильно."- Ник Ходжес Мар 12 ' 12 в 1: 36

@nick действительно. Мой эквивалент "нет проблем, для которых сон является решение."- Дэвид Хеффернан 12 '12 марта в 8: 04

комментарии о Sleep()

  1. это правильный способ запрограммировать задержку?
  2. если ответ да, то почему это лучше, чем вызов Спать ()?

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 ответов


принимая ваши вопросы в свою очередь:

  1. это правильный способ запрограммировать задержку?

да (но и "нет" - см. ниже).

"правильный путь" варьируется в зависимости от конкретных требований и решаемой проблемы. Нет Истина на этом и любой, кто говорит вам иначе, пытается продать вам что-то (перефразировать).

в некоторых случаях ожидание события является правильным механизмом задержки. В других случаях нет.

  1. если ответ да, то почему это лучше, чем вызовом Sleep()?

см. выше: ответ и да. Однако этот второй вопрос просто не имеет смысла, поскольку он предполагает, что Sleep () is всегда и по необходимости никогда не правильный путь,который, как объясняется в ответе на #1 выше, не обязательно имеет место.

Sleep () может не быть лучший или наиболее подходящий способ запрограммировать задержку в все сценарии, но есть сценарии, где это наиболее практично и не имеет существенных недостатков.

почему люди избегают сна () ing

Sleep () является потенциальной проблемой именно потому, что это безусловная задержка, которая не может быть прервана до истечения определенного периода времени. Альтернативные механизмы задержки, как правило, достигают точно такого же результата с разница лишь в том, что существует некий альтернативный механизм возобновления исполнения, отличный от простого течения времени.

ожидание задержки события, пока событие не произойдет (или не будет уничтожено) или определенный период времени прошел.

ожидание мьютекса вызывает задержку, пока мьютекс не будет получен (или уничтожен) или определенный период времени прошел.

etc.

другими словами: Пока некоторые механизмы задержки прерываемы. Sleep () нет. Но если вы ошибаетесь в других механизмах, все еще есть потенциал для возникновения значительных проблем, и часто таким образом, который может быть намного сложнее определить.

Проблемы С Мероприятия.WaitFor () В Этом Случае

прототип в вопросе подчеркивает потенциальную проблему с помощью любой механизм, который приостанавливает выполнение вашего кода, если остальная часть этого кода не реализован способом, который является совместимость с особым подходом:

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

если этот код выполняется в главном потоке, то Таймера1 никогда не будет.

прототип в вопросе выполняет это в потоке, поэтому эта конкретная проблема не возникает, но стоит изучить потенциал, поскольку прототип вводит другую проблему в результате вовлечения этого потока.

By указание бесконечный подождите тайм-аут на вашем WaitFor () в событии приостанавливается выполнение потока до тех пор, пока это событие не произойдет. The TTimer компонент использует механизм таймера на основе сообщений windows, в котором WM_TIMER сообщение подается в очередь сообщений по истечении таймера. Для WM_TIMER сообщение чтобы произойти, ваше приложение должно обрабатывать свою очередь сообщений.

окна таймеры также могут быть созданы, которые обеспечат обратный вызов в другом потоке, что может быть более подходящим подходом в этом (по общему признанию, искусственном) случае. Однако это не возможность, предлагаемая VCL TTimer компонент (по крайней мере, с XE4, и я отмечаю, что вы используете XE2).


сценарий: Вы хотите выполнить несколько последовательных действий с определенной задержкой между ними.

это правильный способ запрограммировать задержку?

Я бы сказал, что есть лучшие способы, см. ниже.

если ответ да, то почему это лучше, чем вызовом Sleep()?

спать в главном потоке-плохая идея: помните, что парадигма windows управляется событиями, i.e выполняйте свою задачу на основе действия и тогда пусть система решает, что делать дальше. Спать в потоке также плохо, так как вы можете остановить важные сообщения из системы (в случае выключения и т. д.).

ваши возможности:

  • обрабатывайте свои действия от таймера в основном потоке, как государственная машина. Следите за состоянием и просто выполните действие, которое представляет это конкретное состояние, когда срабатывает событие таймера. Это работает для кода, который заканчивается за короткое время для каждого таймера событие.

  • поместите строку действий в поток. Используйте тайм-аут события в качестве таймера, чтобы избежать замораживания потока при вызовах сна. Часто эти типы действий связаны с вводом-выводом, где вы вызываете функции со встроенным таймаутом. В этих случаях номер тайм-аута служит естественной задержкой. Так строятся все мои коммуникационные библиотеки.

пример второй вариант:

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. Когда линия действий будет готова, нить будет самоуничтожена. Объект события "global" может использоваться для прерывания действий вручную или во время завершения работы программы.


после энергичного испытания сна против никакого сна... Я решил, что опция без сна гораздо менее склонна давать пользователю ощущение, что в программном обеспечении есть ситуация блокировки.

uses System.Math, System.Types, System.Classes, System.Diagnostics, Vcl.Forms;

// written by Joseph A Poirier, 2016.06-17, converted 2016.12, added try-finally 2017.01-31 
// TThread.sleep(ms) replaced with good old-fashioned math.
// Pause a process, while allowing your forms to continue working,
// move, resize, and close (even mid-process).
procedure handleEvents(Milliseconds: Integer);
 var
   DoneTime, TimeLeft : LongWord;

   sw: TStopwatch;
begin

  DoneTime :=  TThread.GetTickCount + LongWord(Milliseconds);

  TimeLeft := 100;
  sw := TStopwatch.StartNew; // timing entire process
  try
    While ( TimeLeft > 0 ) do
     begin
       TimeLeft := DoneTime - TThread.GetTickCount;

       // continuously check application processes every 3 ms.
       if (TimeLeft mod 3 = 0) or ( TThread.GetTickCount > DoneTime ) then
        begin
         Application.ProcessMessages(); // clear all Vcl messages  // part of Vcl.Forms
         CheckSynchronize(); // check all threaded events
       end;

       if ( TThread.GetTickCount > DoneTime ) then break;
    end;
  finally // ensure stop is executed, whether or not close exceptions occur.
    sw.Stop;
    sw.ElapsedMilliseconds; // captures full run time for debug
  end;

end;

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