0

I wanted to realize a repetitive task in an OmniThreadLibrary worker task, that runs in another thread. The task should be executed every 3 seconds, for example. Therefore I wrote a TOmniWorker descendant with an instance of TTimer as you can see below:

program Project14;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Vcl.ExtCtrls,
  Vcl.Forms,
  OtlTaskControl;

type
  TMyTimerWorker = class(TOmniWorker)
  strict private
    FTimer: TTimer;
    procedure DoOnTimer(Sender: TObject);
  protected
    function Initialize: Boolean; override;
    procedure Cleanup; override;
  end;

{ TMyTimerWorker }

procedure TMyTimerWorker.Cleanup;
begin
  FTimer.Free;
  inherited;
end;

procedure TMyTimerWorker.DoOnTimer(Sender: TObject);
begin
  Beep;
end;

function TMyTimerWorker.Initialize: Boolean;
begin
  Result := inherited;
  if not Result then exit;

  FTimer := TTimer.Create(nil);
  FTimer.OnTimer  := DoOnTimer;
  FTimer.Interval := 3000;
  FTimer.Enabled  := True; // note: this isn't necessary, but is added to avoid hints that 'Enabled' might be 'False'
end;

var
  LTimerWorker: IOmniWorker;
begin
  try
    LTimerWorker := TMyTimerWorker.Create;
    CreateTask(LTimerWorker).Unobserved.Run;
    while True do
      Application.ProcessMessages;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

I set breakpoints in Initialize and DoOnTimer. Former executes well but latter won't be called at all. BTW: Cleanup isn't called neither, so the task is still running.

What am I doing wrong? Is it impossible to use a TTimer in an OTL task? If yes, why?

UPDATE: I found a workaround for TTimer () but why does TTimer approach not work?

Community
  • 1
  • 1
René Hoffmann
  • 2,766
  • 2
  • 20
  • 43
  • possible duplicate of [How to implement thread which periodically checks something using minimal resources?](http://stackoverflow.com/questions/8412254/how-to-implement-thread-which-periodically-checks-something-using-minimal-resour) – David Heffernan Jul 08 '15 at 13:28
  • @DavidHeffernan That question was mainly about how to implement that, my question rather is about why `TTimer` wouldn't work. – René Hoffmann Jul 08 '15 at 13:31
  • If your question was about why `TTimer` would not work then you will need to delete your answer because it does not address that issue at all. – David Heffernan Jul 08 '15 at 13:48
  • 2
    See [Getting a Delphi TTimer to work with a multi-threading app](http://stackoverflow.com/q/10589749/576719). Since the OmniWorker is doing its job in a thread, the messages from the timer are not processed. – LU RD Jul 08 '15 at 13:54
  • 2
    @LURD, that's the answer to this question: TTimer uses the VCL `AllocateHWnd()` function, which is **not thread-safe** and must not be called from outside the context of the main thread. – Johan Jul 08 '15 at 14:02
  • Even if it worked out-of-the-box, I can't imagine any reason to put a timer inside of a thread. Kinda defeats the purpose of a thread. – Jerry Dodge Jul 08 '15 at 14:31
  • @RenéHoffmann Why don't you simply use `Sleep`. Since you are not working on main thread you can easily pause your tread for about 3 seconds. Oh and if you wan't your task to be executed every three seconds you also need to time how much time was required for the task to be processed last time and then use `Sleep(3000-LastProcessingTime)`. Such approach is commonly used in games for maintaining steady game speed. – SilverWarior Jul 08 '15 at 17:50
  • 1
    @SilverWarior: Or use a waitable timer. See `CreateWaitableTimer()` and `SetWaitableTimer()`. – Remy Lebeau Jul 08 '15 at 18:36
  • @SilverWarrior The interval is user-defined. So, it might be that the user specifies an interval of 5 minutes. Termination of the thread would then take up to 5 minutes. – René Hoffmann Jul 09 '15 at 07:26

1 Answers1

3

You TTimer-based code doesn't work because TTimer uses windows messages to trigger the timer event and windows messages are not processed in an OTL worker by default.

Call .MsgWait before .Run and internal worker loop will use MsgWaitForMultipleObjects instead of WaitForMultipleObjects which will allow for message processing.

Saying that, you really should not use TTimer in background tasks because - as others have said - TTimer is not threadsafe.

gabr
  • 26,580
  • 9
  • 75
  • 141
  • 2
    If you really need a thread-safe message-based timer, you can use `SetTimer()` directly instead of `TTimer`, and provide it with a callback function for the message queue to call whenever the timer elapses, instead of processing the `WM_TIMER` message manually. Personally, I would use a waitable timer instead, if not a simple sleep loop. – Remy Lebeau Jul 08 '15 at 18:38