1

Inside a TidHTTPServer.OnCommandGet I create a new object. This Object has a timer that should start immediately but doesnt. The TimerEVent never fires! When I create the object somewhere else it works...

Some code

  TVolumeFader=Class(TObject)

...
constructor TVolumeFader.Create(...);
begin
  inherited Create;
  ...
  
    VolTimer:=TTimer.Create(NIL);
    VolTimer.Enabled:=FALSE;
    VolTimer.Interval:=100;
    VolTimer.OnTimer:=DoTimerTick;
end;

procedure TVolumeFader.DoTimerTick(Sender:TObject);
begin
  LogWrite('TimerTick in VolumeFader',Debug);
  If Assigned(VolTimer)then Begin;
    VolTimer.Enabled:=FALSE;
  End;
  try
    LogWrite('Executing VolumeFade in VolumeFader',Debug);
    VolumeFade;
  finally
    If Assigned(VolTimer)then
      VolTimer.Enabled:=TRUE;
  end;
end;


procedure TMain.OnCommandGet;

Begin;
  TVolumeFader.Create(...);
End;
Mike Torrettinni
  • 1,816
  • 2
  • 17
  • 47
Wolfgang Bures
  • 509
  • 4
  • 12
  • It doesn't work because you create it and then set `Enabled := False` immediately afterward. It helps if you read your code. :-) The `OnTimer` event doesn't happen when the timer is disabled. You can find these sorts of issues pretty easily using the debugger to step through the code. You've also got typos in the code you posted, which tells me you didn't copy/paste your own code. Please do not retype code into your post, as it can easily introduce new errors that can be misleading or cause incorrect answers to be posted. – Ken White Sep 11 '20 at 17:58
  • 1
    @KenWhite even if the timer were enabled, it still wouldn't work as shown, because the thread that the timer is created in doesn't have a message loop – Remy Lebeau Sep 11 '20 at 18:09
  • @RemyLebeau: I missed it being an Indy related question. But the primary reason it isn't working in the code that the user posted above is that it's never enabled. – Ken White Sep 11 '20 at 18:12

1 Answers1

2

In your object's constructor, you are creating a TTimer ok, but you are setting its Enabled property to False. So make sure you actually activate the timer once the constructor has exited. Or else change False to True in the constructor.

That being said, your code still won't work as shown. This is because TIdHTTPServer is a multi-threaded component, its OnCommand... events are fired in the context of worker threads that TIdHTTPServer creates for itself when clients connect to the server. But TTimer is a message-based timer, it creates an internal HWND for itself which is tied to the thread that it is created in, and that thread must have a message loop in order for TTimer to process WM_TIMER messages. The worker thread that you are creating your TTimer in does not have a message loop, so the TTimer will not be able to fire its OnTimer event.

So, you will have to either:

  1. run your own message loop inside of the OnCommand... event handler after creating your object, and then free the object before the event handler exits. There is no guarantee that the calling thread will continue running once the OnCommand... event handler has exited. Just note that the client will be blocked from sending any further HTTP commands to the server while the timer is running:
procedure TMain.OnCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  fader: TVolumeFader;
  msg: tagMSG;
begin
  ...
  fader := TVolumeFader.Create(...);
  try
    while (timer should keep running) do
    begin
      //Application.ProcessMessages;
      if PeekMessage(@msg, 0, 0, 0, PM_REMOVE) then
      begin
        TranslateMessage(@msg);
        DispatchMessage(@msg);
      end else
        Sleep(100);
    end;
  finally
    fader.Free;
  end;
  ...
end;
  1. delegate the creation of your object, and thus its TTimer, to your main UI thread, so that your OnTimer event handler will be fired in the context of that thread rather than in the context of the server's worker thread. Just make sure that your OnTimer code is thread-safe, if it needs to access anything that is shared with the server:
procedure TMain.OnCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  ...
  TThread.Synchronize(nil, // or TThread.Queue()
    procedure
    begin
      TVolumeFader.Create(...); // when do you destroy this object?
    end
  );
  ...
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the advice. Synchronize works perfectly! @`Enabled`: Sorry for the missing code piece, `Enabled` is set to 'true' as soon as all callback events are set. @`Destroy`: The object destroys itself after completing it's job. – Wolfgang Bures Sep 12 '20 at 09:26