【问题标题】:Timer Queue in Windows ServiceWindows 服务中的定时器队列
【发布时间】:2014-09-02 09:18:49
【问题描述】:

对于 Windows 服务,我需要一个计时器来定期执行某项任务。当然,有很多选项看起来优于定时器(多线程,直接从服务的主线程调用方法),但在这种特殊情况下它们都有其缺点。

但是,出于显而易见的原因,SetTimer() 不能在没有 GUI 的消息队列的情况下工作。我所做的(在 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 之外,这按预期工作。

我对您的反馈很感兴趣,但是,如果我的实现是正确的 - 我只是想避免弄乱其他应用程序的消息和其他我不知道的副作用,因为我没有处理消息的经验。

谢谢!

【问题讨论】:

  • 我会创建一个线程并等待取消事件指定的时间
  • @DavidHeffernan 我同意,出于一般考虑,我也更喜欢一个线程,但这在这里有它的缺点......只是想知道我的计时器实现是否很好:)
  • 你没有展示你的实现。你没有显示循环。
  • @DavidHeffernan 已编辑,谢谢!
  • 这是世界上最糟糕的事情。你有一个有点虚假的消息循环。你仍然有你等待的事件。你只是不需要计时器。删除所有这些并使用等待事件作为计时器。

标签: delphi winapi timer message-queue freepascal


【解决方案1】:

我会选择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) 有什么优势?
  • 在这个简单的例子中,不多。但是如果真正的项目需要等待除了计时器和终止事件之外的其他事情怎么办?或者在等待计时器触发的同时做其他事情?那么等待计时器就更有意义了。
  • 并非如此。等待 N 件事超时。如果由于超时而等待返回,则计时器已过。
  • 如果您需要定期发生一些事情,这就是计时器的用途。如果您必须等待除计时器之外的多个事情,那么使用与计时器间隔相同的循环/等待间隔通常没有意义,因为您可能必须在计时器信号之间做其他事情。你为什么认为可等待计时器首先存在?
  • 我想这取决于您是希望时钟在工作完成后开始滴答作响,还是在开始之前开始。
【解决方案2】:

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

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

脉冲周期将是间隔加上完成工作所需的时间。如果您需要一个特定的时间间隔,并且这项工作需要大量时间,那么 Remy 建议的可等待计时器就是您要做的事情。

不惜一切代价,您真正不想做的是使用基于消息循环的计时器。这不适合服务。

【讨论】:

  • 哪里/什么是 TerminateEventObject?我在 Delphi 单位中找不到它。
  • @Delphi 不,它不存在。这是在提问者程序中定义的事件。
  • 什么事件之王有WaitFor功能?!我在 TThread 上见过 WaitFor。
  • @Delphi TEvent 或 TSimpleEvent
猜你喜欢
  • 1970-01-01
  • 2018-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-02
  • 2014-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多