Search code examples
multithreadingwaitdelphi-10-seattle

Restart TEvent.WaitFor without exiting it


Is it possible to restart TEvent.WaitFor without exiting it? I need to start waiting again when _interval was changed by setter. For example, when one hour was set and I would like to change interval to 15 seconds, changes will come into effect when one hour is elapsed.

_terminatingEvent: TEvent;

procedure TTimerThread.Execute();
begin
  inherited;
  while not Terminated do begin
    try
      _terminatingEvent.WaitFor(_interval);
      if Assigned(_onTimer) and _enabled then _onTimer(Self);
    except
      on ex: Exception do _logError(ex);
    end;
  end;
end;

procedure TTimerThread.TerminatedSet();
begin
  _terminatingEvent.SetEvent();
end;

procedure TTimerThread._setInterval(const Value: Integer);
begin
  _interval := Value;
  //Restart WaitFor here
end;

Currently I "solved" the issue in a following way:

procedure TTimerThread.Execute();
begin
  inherited;
  while not Terminated do begin
    try
      if _terminatingEvent.WaitFor(_interval) = wrTimeout then
        if Assigned(_onTimer) and _enabled then _onTimer(Self);
    except
      on ex: Exception do _logError(ex);
    end;
  end;
end;

procedure TTimerThread.TerminatedSet();
begin
  _terminatingEvent.SetEvent();
end;

procedure TTimerThread._setInterval(const Value: Integer);
begin
  _interval := Value;
  _terminatingEvent.ResetEvent();
end;

It seems that when I use SetEvent instead of ResetEvent, the "set" state is saved permanently and CPU usage jumps to 100%.


Solution

  • You can use two TEvent objects, one for the timer and one for the setter, eg:

    type
      TTimerThread = class(TThread)
      private
        _terminatingEvent: TEvent;
        _updatedEvent: TEvent;
        ...
      protected
        procedure Execute; override;
        procedure TerminatedSet; override;
      public
        constructor Create(ASuspended: Boolean); reintroduce;
        destructor Destroy; override;
      end;
    
    constructor TTimerThread.Create(ASuspended: Boolean);
    begin
      inherited Create(ASuspended);
      _terminatingEvent := TEvent.Create(nil, True, False, '');
      _updatedEvent := TEvent.Create(nil, False, False, '');
    end;
    
    destructor TTimerThread.Destroy;
    begin
      _terminatingEvent.Free;
      _updatedEvent.Free;
      inherited;
    end;
    
    procedure TTimerThread.Execute;
    var
      Arr: THandleObjectArray;
      SignaledObj: THandleObject;
    begin
      SetLength(Arr, 2);
      Arr[0] := _terminatingEvent;
      Arr[1] := _updatedEvent;
    
      while not Terminated do
      begin
        try
          case THandleObject.WaitForMultiple(Arr, _interval, False, SignaledObj) of
            wrSignaled: begin
              if (SignaledObj is TEvent) then (SignaledObj as TEvent).ResetEvent();
            end;
            wrTimeOut: begin
              if Assigned(_onTimer) and _enabled then
                _onTimer(Self);
            end;
            wrError: begin
              RaiseLastOSError;
            end;
          end;
        except
          on ex: Exception do
            _logError(ex);
        end;
      end;
    end;
    
    procedure TTimerThread.TerminatedSet;
    begin
      inherited;
      _terminatingEvent.SetEvent;
    end;
    
    procedure TTimerThread._setInterval(const Value: Integer);
    begin
      if _interval <> Value then
      begin
        _interval := Value;
        _updatedEvent.SetEvent;
      end;
    end;