【问题标题】:Is there a way to sleep unless a message is received?除非收到消息,否则有没有办法睡觉?
【发布时间】:2010-07-28 17:38:38
【问题描述】:

我在一个主循环如下所示的服务中工作:

while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
begin
  Self.ServiceThread.ProcessRequests(false);
  ProcessFiles;
  Sleep(3000);      
end;

ProcessRequests 很像Application.ProcessMessages。我不能将true 传递给它,因为如果我这样做了,它就会阻塞,直到收到来自 Windows 的消息,并且 ProcessFiles 不会运行,它必须不断运行。睡眠可以降低 CPU 使用率。

在我尝试从 Windows 的服务管理列表中关闭该服务之前,这一切正常。当我点击 Stop 时,它会发送一条消息并期望几乎立即得到响应,如果它在 Sleep 命令的中间,Windows 会给我一个错误,表明该服务没有响应 Stop 命令。

所以我需要说“睡眠 3000 或直到您收到消息,以先到者为准。”我确定有一个 API,但我不确定它是什么。有人知道吗?

【问题讨论】:

  • 我在我的服务中就是这样做的,但要保持睡眠时间短。 CPU 仍然被调度程序保持关闭。尝试睡眠(500)
  • 您想在收到消息之前在后台线程中睡觉,或者您想冻结您的前台线程?在这里启发我。
  • 你可以试试 SleepEx 或 WaitForMultipleObjects,正如 Jeroen 建议的那样。 SleepEx 专为可中断睡眠而设计。

标签: windows delphi winapi service


【解决方案1】:

这类东西很难搞定,所以我通常从 MSDN 的 API 文档开始。

WaitForSingleObject 文档针对这些情况专门针对MsgWaitForMultipleObjects

调用等待时要小心 直接或 间接创建窗口。如果一个 线程创建任何窗口,它必须 处理消息。消息广播 发送到系统中的所有窗口。 使用等待函数的线程 没有超时间隔可能会导致 系统陷入僵局。二 间接的代码示例 创建窗口是 DDE 和 CoInitialize 函数。因此,如果 你有一个线程创建 窗户,使用MsgWaitForMultipleObjectsMsgWaitForMultipleObjectsEx,而不是 比WaitForSingleObject.

MsgWaitForMultipleObjects 中,您有一个dwWakeMask 参数指定要返回的排队消息,以及一个描述您可以使用的掩码的表格。

编辑因为Warren P的评论:

如果由于ReadFileExWriteFileExQueueUserAPC 而您的主循环可以继续,那么您可以使用SleepEx

--杰罗恩

【讨论】:

  • 这是明智之举。其他方式并不是在后台线程中真正“等待”,它们会定期在前台线程中执行某些操作。这两件事不是做同一件事的替代品,它们是完全不同的事情。使用线程或不使用线程,但如果您正在使用线程并等待消息,则确实需要它是可中断的。 SleepEx 也是可中断的。
【解决方案2】:

MsgWaitForMultipleObjects() 是要走的路,即:

while (fServer.ServerState = ssStarted) and (not Self.Terminated) do 
begin 
  ProcessFiles; 
  if MsgWaitForMultipleObjects(0, nil, FALSE, 3000, QS_ALLINPUT) = WAIT_OBJECT_0 then
    Self.ServiceThread.ProcessRequests(false); 
end;

如果您想以 3 秒的间隔调用 ProcessFiles() 而不管是否有任何消息到达,那么您可以为此使用等待计时器,即:

var
  iDue: TLargeInteger;
  hTimer: array[0..0] of THandle;
begin
  iDue := -30000000; // 3 second relative interval, specified in nanoseconds
  hTimer[0] := CreateWaitableTimer(nil, False, nil);
  SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
  while (fServer.ServerState = ssStarted) and (not Self.Terminated) do 
  begin 
    // using a timeout interval so the loop conditions can still be checked periodically
    case MsgWaitForMultipleObjects(1, hTimer, False, 1000, QS_ALLINPUT) of
      WAIT_OBJECT_0:
      begin
        ProcessFiles;
        SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
      end;
      WAIT_OBJECT_0+1: Self.ServiceThread.ProcessRequests(false);
    end;
  end;
  CancelWaitableTimer(hTimer[0]);
  CloseHandle(hTimer[0]);
end;

【讨论】:

    【解决方案3】:

    使用计时器来运行 ProcessFiles 而不是将其侵入主应用程序循环。然后 ProcessFiles 将在您想要的时间间隔内运行,消息将得到正确处理,不会占用 100 % CPU。

    【讨论】:

    • 这是我尝试的第一件事,但没有成功。我把它拿出来并用睡眠循环替换它,我同意这是一个丑陋的黑客。但我只是用计时器再次尝试,(自从我这样做以来发生了很多变化)现在它可以工作了。去图吧。
    • 这不适用于服务
    • @mj2008:最初这根本不应该是一项服务。但是你知道范围蔓延是如何... :(
    • 也许在这种情况下,但是一旦您开始使用线程,TTimer 就会给您带来很多惊喜。
    • 碰巧在 VCL 应用程序中触发的周期性 TTimer 是真正有趣的东西的潜伏来源。尤其是在您认为应该在应用程序代码的某处添加对 Application.ProcessMessages 的调用的那一天。记住这一天,当下一个奇怪的错误出现时。
    【解决方案4】:

    我在多线程应用程序中使用了 TTimer,但结果很奇怪,所以现在我使用事件。

    while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
    begin
      Self.ServiceThread.ProcessRequests(false);
      ProcessFiles;
    
      if ExitEvent.WaitFor(3000) <> wrTimeout then
        Exit;   
     end;
    

    您使用

    创建事件
    ExitEvent := TEvent.Create(nil, False, False, '');
    

    现在最后一件事是在服务停止的情况下触发事件。我认为服务的 Stop 事件是放置这个的正确位置。

    ExitEvent.SetEvent;
    

    我将此代码用于我的数据库连接池系统中的清理线程,但它也应该适用于您的情况。

    【讨论】:

    • 这与原始问题存在相同的问题 - 它正在等待整整 3 秒,因为无法发出 ExitEvent 信号,直到调用 ProcessRequests() 来处理 SCM 的停止请求。
    【解决方案5】:

    您无需睡眠整整 3 秒即可保持较低的 CPU 使用率。即使像 Sleep(500) 这样的东西也应该让你的使用率保持在很低的水平(如果没有等待处理的消息,它应该很快通过循环并再次进入睡眠状态。如果你的循环需要几毫秒才能运行它仍然意味着你的线程大部分时间都在睡觉。

    话虽如此,您的代码可能会从一些重构中受益。您说您不希望 ProcessRequests 阻止等待消息?该循环中唯一的另一件事是 ProcessFiles。如果这取决于正在处理的消息,那么为什么它不能阻塞?如果它不依赖于正在处理的消息,那么它可以拆分到另一个线程吗? (之前通过计时器触发 ProcessFiles 的建议是一个很好的建议)。

    【讨论】:

      【解决方案6】:

      使用一个 TEvent 来指示线程何时应该唤醒。然后阻止 tevent(如果您有多个事件要等待,请使用 Jeroen 所说的 waitformultiple)

      【讨论】:

        【解决方案7】:

        不能将 ProcessFiles 移动到单独的线程吗?在您的 MainThread 中,您只需等待消息,当服务终止时,您终止 ProcessFiles 线程。

        【讨论】:

        • 已经涵盖了。 ProcessFiles 基本上只是查看是否有要处理的文件并将它们交给另一个线程。
        • @Mason Wheeler:这不会阻止您将 ProcessFiles 移动到另一个线程,或者让文件处理线程在完成后自己调用 ProcessFiles。
        猜你喜欢
        • 2015-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-23
        • 1970-01-01
        • 2019-11-25
        • 2018-03-10
        • 2011-09-07
        相关资源
        最近更新 更多