4

For a Windows Service, I need a timer to perform a certain task regularly. Of course, there are many options that seem superior to a timer (multithreading, calling method directly from the service's main thread), but they all have their disadvantages in this particular situation.

However, for obvious reasons, SetTimer() does not work without the message queue of a GUI. What I have done (in Free Pascal) is the following:

Create the timer:

MyTimerID := SetTimer(0, 0, 3333, @MyTimerProc);

In the main loop of the service, run the timer queue:

procedure TMyServiceThread.Execute;
var
  AMessage: TMsg;
begin
  repeat
    // Some calls
    if PeekMessage(AMessage, -1, WM_TIMER, WM_TIMER, PM_REMOVE) then begin
      TranslateMessage(AMessage);
      DispatchMessage(AMessage);
    end;
    // Some more calls
    TerminateEventObject.WaitFor(1000);
  until Terminated;
end;

And at the end, kill the timer:

KillTimer(0, MyTimerID)

Except of KillTimer always returning False, this works as anticipated.

I am interested in your feedback, however, if my implementation is correct - I just want to avoid messing with other application's messages and other side effects I am not aware of because of my inexperience with message handling.

Thanks!

LeRookie
  • 311
  • 3
  • 12
  • 1
    I'd create a thread and wait on a cancellation event for a specified timeoiut – David Heffernan Jul 11 '14 at 17:08
  • @DavidHeffernan I agree, I'd prefer a thread for general considerations too, but this has it's disadvantages here ... just wondering if my timer implementation is fine :) – LeRookie Jul 11 '14 at 17:59
  • You didn't show your implementation. You didn't show the loop. – David Heffernan Jul 11 '14 at 18:04
  • That's the worst of all worlds. You've got a somewhat bogus message loop. And you still have the event on which you wait. You just don't need a timer. Remove all that and use waiting on the event as a timer. – David Heffernan Jul 11 '14 at 18:22

2 Answers2

6

I would opt for a waitable timer. No message queue is needed.

function WaitableTimerDelayFromMilliseconds(milliseconds: Integer): TLargeInteger;
begin
  Result := 0 - (TLargeInteger(milliseconds) * 10000);
end;

procedure TMyServiceThread.Execute;
var
  TimerInterval: Integer;
  DueTime: TLargeInteger;
  hTimer: THandle;
  Handles: array[0..1] of THandle;
begin
  TimerInterval := 10000; // use whatever interval you need
  DueTime := WaitableTimerDelayFromMilliseconds(TimerInterval);

  hTimer := CreateWaitableTimer(nil, FALSE, nil);
  if hTimer = 0 then RaiseLastOSError;
  try
    if not SetWaitableTimer(hTimer, DueTime, TimerInterval, nil, nil, False) then RaiseLastOSError;
    try
      Handles[0] := TerminateEventObject.Handle;
      Handles[1] := hTimer;

      while not Terminated do
      begin
        case WaitForMultipleObjects(2, PWOHandleArray(@Handles), False, INFINITE) of
          WAIT_FAILED:
            RaiseLastOSError;
          WAIT_OBJECT_0+0:
            Terminate;
          WAIT_OBJECT_0+1:
          begin
            // do your work
          end;
        end;
      end;
    finally
      CancelWaitableTimer(hTimer);
    end;
  finally
    CloseHandle(hTimer);
  end;
end;

Update: Or, as David Heffernan suggests, you could just wait on the termination event by itself:

procedure TMyServiceThread.Execute;
var
  TimerInterval: Integer;
begin
  TimerInterval := 10000; // use whatever interval you need

  while not Terminated do
  begin
    case TerminateEventObject.WaitFor(TimerInterval) of
      wrSignaled:
        Terminate;
      wrTimeout:
      begin
        // do your work
      end;
      wrError:
        RaiseLastOSError;
    end;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I cannot see the point of this. What is the advantage of this over `TerminateEventObject.WaitFor(TimerInterval)`? – David Heffernan Jul 11 '14 at 22:45
  • In this simple example, not much. But what if the real project needs to wait on other things besides just a timer and a termination event? Or do other things while waiting for the timer to trigger? Then a waitable timer makes more sense. – Remy Lebeau Jul 11 '14 at 22:56
  • Not really. Wait on N things with a timeout. If the wait returns because of timeout, then timer has elapsed. – David Heffernan Jul 11 '14 at 22:58
  • If you need something to happen at regular intervals, that is what a timer is for. If you have to wait on multiple things besides a timer, it does not usually make sense to use a loop/wait interval that is the same as the timer interval, as you may have to do other things in between timer signals. Why do you think waitable timers exist in the first place? – Remy Lebeau Jul 11 '14 at 23:03
  • I guess it comes down to whether you want the clock to start ticking after the work finishes, or before it starts. – David Heffernan Jul 11 '14 at 23:12
2

As discussed in the comments, you may not need a timer at all. You can simply use the timeout of the wait on your event to create a regular pulse:

while not Terminated do
begin
  case TerminateEventObject.WaitFor(Interval) of
  wrSignaled:
    break;
  wrTimeout:
    // your periodic work goes here
  wrError:
    RaiseLastOSError;
  end;
end;

The period of the pulse will be the interval plus the time taken to do the work. If you need a specific interval, and the work takes significant time, the Remy's suggestion of a waitable timer is the thing to do.

What you really don't want to do, at all costs, is use a message loop based timer. That's not appropriate for a service.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490