问题
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!
回答1:
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.
回答2:
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;
来源:https://stackoverflow.com/questions/24702965/timer-queue-in-windows-service