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

3
我需要在我的应用程序中使用高分辨率(比1毫秒更准确)的计时。Windows中的可等待定时器可以(或可以被制成)精确到毫秒,但如果我需要精确的周期性,例如35.7142857141毫秒,即使具有36毫秒周期的可等待定时器也会很快失去同步。
我对这个问题的“解决方案”(用引号括起来是因为它并不完全正确)是使用一系列单次定时器,其中我使用每个定时器的到期来调用下一个定时器。通常,这样的过程会随着时间的推移而累积误差,但在每个定时器回调中,我检查当前时间(使用System.Diagnostics.Stopwatch),并使用它来计算下一个定时器的周期(因此,如果定时器稍微延迟到期,下一个定时器将自动缩短周期以进行补偿)。
这正如预期的那样工作,但是大约10-15秒后,定时器系统似乎变得阻塞,并且有些定时器回调偶尔会晚到25到100毫秒。几秒钟后,问题就消失了,一切都再次顺利地运行了10-15秒,然后又出现了口吃。
由于我正在使用Stopwatch设置每个计时器周期,因此我也在使用它来监视每个计时器回调的到达时间。在平稳运行的期间,大多数(可能95%)的间隔为35或36毫秒,并且没有任何间隔距离预期的35.7142857143超过5毫秒。
在“口吃”的阶段,间隔的分布非常接近,但有很少量的间隔异常大(在大约3秒的时间内,有几个间隔大于60毫秒和一两个间隔长于100毫秒)。这种口吃非常明显,如果可能,我正在尝试修复它。
对于高分辨率计时器,我使用了极古老的timeSetEvent()多媒体计时器从winmm.dll中获取。在解决这个问题时,我切换到使用CreateTimerQueueTimer(以及timeBeginPeriod设置高分辨率),但是我在两种计时器机制中都遇到了同样的问题。我尝试使用CreateTimerQueueTimer的各种标志进行实验,以确定计时器运行的线程,但无论如何,口吃都会出现。
这只是使用定时器的基本问题吗(即使用每个单次定时器调用下一个)?如果是这样,我还有其他选择吗?我正在考虑的一件事是确定在需要重置计时器之前,连续多少个1毫秒精度的滴答声会使我保持在某些任意精度限制内。因此,例如,如果我想要35.71428周期,我可以让36毫秒计时器过去15次,然后它就会偏离5毫秒,然后杀死它并启动一个新的计时器。

1
你需要一个实时操作系统!在(繁重的)磁盘活动下,它的表现如何? - H H
@Henk:在磁盘活动量大的情况下,它会出现卡顿,但是我的电脑上播放任何东西(视频、音乐等)都会出现这种情况。 - MusiGenesis
2个回答

3

考虑到.NET的限制,我认为你的方法不错。你的进程优先级是多少?我认为它需要比普通进程更高,以避免其他进程进行磁盘活动。

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

你使用的操作系统是什么?我也同意Henk的观点,实时操作系统是最佳解决方案。根据我所读的资料,Windows CE可以作为实时操作系统,但我无法发表更多评论。


你的回答让我思考了。我一直在设置计时器线程的优先级,但我没有关注到我的进程优先级。在 .Net 中,你可以设置当前进程的 PriorityClass,但当我将其设置为 AboveNormalHighRealtime 时,问题变得更加严重——所有计时器回调都会延迟 50-100 毫秒。这是相当反直觉的。幸运的是,Process 类还有一个 PriorityBoostEnabled 属性,它可以在主窗口获得焦点时暂时提高进程优先级。将其设置为 true 可以解决我的问题。 - MusiGenesis
1
我对优先级类别的唯一猜测是,也许提高自己进程的优先级会将其置于计时器机制本身的优先级之上,这意味着您自己的应用程序正在警觉地等待现在被延迟的计时器事件。 - MusiGenesis
我猜我说得太早了 - 问题仍然存在。有时候问题只是暂时消失了一段时间,这让我觉得我已经解决了它,但实际上并没有。 - MusiGenesis
这很有趣。你可能想看看当.NET执行GC时,你的应用程序会如何表现。你可以使用垃圾回收通知http://msdn.microsoft.com/en-us/library/cc713687.aspx来对此过程进行一些控制。你还可以使用`GC.AddMemoryPressure()`模拟一些内存压力。 - Paul Williams
我认为你是对的,我会尝试看看GC对此有什么影响。我现在明白了为什么提高进程优先级不起作用 - 较低优先级的计时器进程不允许“打扰”我的较高优先级应用程序进程,因此回调被延迟了。我认为我实际上需要提高计时器的优先级。 - MusiGenesis

1

计时器并不便宜,使用1毫秒计时器来接近35.7毫秒的计时器会消耗你的利润。我认为你可能已经达到了PC本身可以实现的极限。
我建议使用更长时间的计时器(35或36),并用秒表计时器进行校正。

为了引起一些争议,我不确定.NET框架是否是这种任务最合适的平台。你有考虑过GC如何满足你的需求吗?


@Henk:我可能解释不清楚,但我确实使用了长达35到36毫秒的较长时间(一次性)定时器。1毫秒只是计时器的分辨率,而不是其周期。 - MusiGenesis
此外,.NET框架在这种情况下的主要优势是它已经是应用程序使用的语言。我们称之为“现有优势”。 :) - MusiGenesis

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