Windows 中的可等待计时器问题(timeSetEvent 和 CreateTimerQueueTimer)

Posted

技术标签:

【中文标题】Windows 中的可等待计时器问题(timeSetEvent 和 CreateTimerQueueTimer)【英文标题】:Problem with waitable timers in Windows (timeSetEvent and CreateTimerQueueTimer) 【发布时间】:2010-03-25 13:41:51 【问题描述】:

我的应用程序需要高分辨率(比 1 毫秒更准确)的计时。 Windows 中的可等待计时器(或可以制成)精确到毫秒,但如果我需要一个精确的周期,例如 35.7142857141 毫秒,即使是 36 毫秒周期的可等待计时器也会很快失去同步。

我对这个问题的“解决方案”(带有讽刺意味的引号,因为它工作不正常)是使用一系列一次性计时器,我使用每个计时器的到期时间来调用下一个计时器。通常这样的过程会随着时间的推移而累积错误,但在每个计时器回调中,我检查当前时间(使用System.Diagnostics.Stopwatch)并使用它来计算下一个计时器的周期需要是多少(所以如果一个计时器碰巧过期有点晚,下一个计时器会自动有更短的时间来补偿)。

这按预期工作,除了可能在 10 到 15 秒后,计时器系统似乎陷入困境,并且这里和那里的一些计时器回调延迟了 25 到 100 毫秒。几秒钟后问题消失了,一切又顺利运行了 10-15 秒,然后又出现了口吃。

由于我使用Stopwatch 设置每个定时器周期,我还使用它来监控每个定时器回调的到达时间。在平稳运行期间,大多数(可能 95%)间隔为 35 或 36 毫秒,并且没有任何间隔与预期的 35.7142857143 相差超过 5 毫秒。

在“故障”伸展期间,间隔的分布几乎相同,除了极少数非常大(在可能 3 秒的伸展期间,有几个超过 60 毫秒,有一个或两个超过 100 毫秒) )。这种口吃非常明显,如果可能的话,这就是我正在尝试解决的问题。

对于高分辨率计时器,我使用了来自winmm.dll 的极其古老的timeSetEvent() 多媒体计时器。为了解决这个问题,我改用CreateTimerQueueTimer(连同timeBeginPeriod 来设置高分辨率),但我发现两种计时器机制都存在同样的问题。我尝试使用CreateTimerQueueTimer 的各种标志进行试验,这些标志确定计时器在哪个线程上运行,但无论如何都会出现口吃。

这只是以这种方式使用计时器的一个基本问题(即使用每个一次性计时器来调用下一个计时器)吗?如果是这样,我还有其他选择吗?我正在考虑的一件事是,在我需要重置计时器之前,确定有多少个连续的 1 毫秒精度的滴答声将使我保持在某个任意精度限制内。因此,例如,如果我想要一个 35.71428 的周期,我可以让一个 36 毫秒的计时器在它关闭 5 毫秒之前经过 15 次,然后终止它并启动一个新的。

【问题讨论】:

您需要一个实时操作系统!这在(大量)磁盘活动下如何保持? @Henk:它在磁盘活动繁重的情况下会卡顿,但在我的计算机上播放任何东西(视频、音乐等)也是如此。 【参考方案1】:

鉴于 .NET 的限制,我认为您有一个很好的方法。您的流程优先级是什么?我认为它需要高于正常水平以避免其他进程进行磁盘活动。

我同意 Henk 的观点,即 .NET 框架不是这里的最佳解决方案。如果发生垃圾回收,可能需要一段时间来释放对象、压缩堆等。

您使用的是什么操作系统?我也同意 Henk 的观点,即实时操作系统是最好的解决方案。根据我所阅读的内容,Windows CE 符合实时操作系统的要求,但我无法进一步评论。

【讨论】:

你的回答让我思考。我正在设置计时器线程的优先级,但我没有对我的进程的优先级做任何事情。在 .Net 中,您可以设置当前进程'PriorityClass,但是当我将其设置为 AboveNormalHighRealtime 时,它使我的问题变得更糟 - all 计时器回调最终会延迟 50-100 毫秒。至少可以说是违反直觉的。幸运的是,Process 类还有一个PriorityBoostEnabled 属性,当主窗口获得焦点时,它会临时提高进程的优先级。将此设置为 true 似乎可以解决我的问题。 我对优先级的唯一猜测是,可能提高您自己进程的优先级会将其置于计时器机制本身的优先级之上,这意味着您自己的应用程序正在警觉地等待现在延迟很多的计时器事件. 我想我说得太早了——问题仍然存在。有时问题会消失一段时间,这让我觉得我已经解决了,而我还没有解决。 这很有趣。您可能希望了解 .NET 执行 GC 时应用程序的行为。您可以使用垃圾收集通知msdn.microsoft.com/en-us/library/cc713687.aspx 对此过程进行一些控制。您还可以使用GC.AddMemoryPressure() 模拟一些内存压力。 我认为你是对的,我会尝试看看 GC 对此有何影响。我现在明白为什么提高进程优先级不起作用 - 较低优先级的计时器进程不允许“打扰”我的高优先级应用程序进程,因此回调会延迟。我想我实际上需要提高计时器的优先级。【参考方案2】:

计时器并不便宜,并且通过使用 1 毫秒计时器来接近 35.7 计时器,您在这里会吃掉自己的利润。我认为您可能处于 PC 本身所能达到的极限。 我会选择更长时间的计时器(35 或 36)并使用秒表计时器进行校正。

为了引发一些争议,我不太确定 .NET 框架是最适合此类任务的平台。您是否想过 GC 如何满足您的需求?

【讨论】:

@Henk:我可能误解了这一点,但我使用了大约 35 到 36 毫秒的更长周期(一次性)计时器。 1 ms 只是计时器的分辨率,而不是它的周期。 此外,.NET 框架在这种情况下的主要优势是它是应用程序已经编写的语言。我们称之为“在职优势”。 :)

以上是关于Windows 中的可等待计时器问题(timeSetEvent 和 CreateTimerQueueTimer)的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 服务的 timer_elapsed 事件处理程序中使用异步等待

windows是啥?

Windows服务的安装及配合定时器编写简单的程序

让可等待的计时器添加APC调用

如何使用计时器等待?

等待计时器在 Java 中完成