Windows服务中的计时器队列

4
对于Windows服务,我需要一个定时器来定期执行某项任务。当然,有许多选项似乎比定时器更优越(多线程,直接从服务的主线程调用方法),但在这种特定情况下,它们都有缺点。
然而,由于明显的原因,没有GUI的消息队列,SetTimer()无法工作。我已经在Free Pascal中完成了以下操作:
创建定时器:
MyTimerID := SetTimer(0, 0, 3333, @MyTimerProc);

在服务的主循环中,运行计时器队列:
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;

最后,停止计时器:

KillTimer(0, MyTimerID)

除了KillTimer总是返回False之外,这个代码如预期一样工作。但我很想听取你的反馈,确保我的实现是正确的——我只是想避免因为我对消息处理的经验不足而影响其他应用程序的消息和产生其他副作用。谢谢!

1
我会创建一个线程,并等待指定时间的取消事件。 - David Heffernan
@DavidHeffernan 我同意,我也更喜欢一个用于一般考虑的线程,但这在这里有它的缺点...只是想知道我的计时器实现是否正确 :) - LeRookie
你没有展示你的实现。你没有展示循环。 - David Heffernan
@DavidHeffernan 编辑过,谢谢! - LeRookie
这是最糟糕的情况。你有一个有点虚假的消息循环。而且你仍然有等待的事件。你只是不需要一个计时器。移除所有这些,使用等待事件作为计时器。 - David Heffernan
2个回答

6
我建议使用可等待定时器(waitable timer)。不需要消息队列。您可以查看此处了解更多信息。
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;

更新: 或者,正如David Heffernan建议的那样,您可以只等待终止事件本身:

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;

我看不出这有什么意义。相较于 TerminateEventObject.WaitFor(TimerInterval),这有什么优势? - David Heffernan
在这个简单的例子中,没有太多的东西。但是如果真正的项目需要等待除了计时器和终止事件之外的其他事情呢?或者在等待计时器触发时做其他事情?那么可等待计时器就更有意义了。 - Remy Lebeau
不是真的。等待N个事物并设置超时。如果等待因超时而返回,则计时器已经过去。 - David Heffernan
如果您需要定期发生某些事情,那么计时器就派上用场了。如果除了计时器之外,您还必须等待其他多个事情,使用与计时器间隔相同的循环/等待间隔通常是没有意义的,因为您可能需要在计时器信号之间执行其他任务。您认为为什么要首次引入可等待计时器呢? - Remy Lebeau
我想这取决于你是希望在工作完成后再开始计时,还是在工作开始前就开始计时。 - David Heffernan

2

正如评论中所讨论的那样,您可能根本不需要计时器。您可以简单地使用等待事件的超时来创建定期脉冲:

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

脉冲周期将是间隔加上完成工作所需的时间。如果您需要特定的间隔,并且工作需要较长时间,则Remy建议使用可等待计时器。

真正不应该做的事情是不惜一切代价使用基于消息循环的计时器。这对服务来说不合适。


终止事件对象在哪里/是什么?我在Delphi单元中找不到它。 - Delphi.Boy
@Delphi 不是的,你要找的是在提问者的程序中定义的事件。 - David Heffernan
什么样的事件有WaitFor函数?我在TThread上看到过WaitFor。 - Delphi.Boy
@Delphi TEvent或TSimpleEvent - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接