如何在C#中使用CreateTimerQueueTimer创建高分辨率定时器?

7

我使用了Windows多媒体dll创建了一个高分辨率计时器,使用了 timeSetEvent() 方法。

但是 timeSetEvent() 页面建议使用:

CreateTimerQueueTimer()

如何在C#中使用CreateTimerQueueTimer()每10毫秒执行一次方法?

3个回答

9
这里有一个C#封装CreateTimerQueueTimer的链接: http://social.msdn.microsoft.com/Forums/en-CA/csharpgeneral/thread/822aed2d-dca0-4a8e-8130-20fab69557d2 (向下滚动到Hobz的最后一篇帖子中获取示例类)
我刚试了一下,它可以正常工作。但需要注意的是,在启动定时器之前需要调用timeBeginPeriod(1)来设置您的系统为高分辨率。因为timeSetEvent内部会调用timeBeginPeriod,这就是为什么有些人错误地认为它创建了更高分辨率的定时器。

1
@MusiGenesis- 我尝试使用这段代码,但是我遇到了堆栈溢出异常。你说的“调用timeBeginPeriod(1)”是什么意思?我找不到这样的函数。 - sura

3

CreateTimerQueueTimer函数传递的回调函数应该是一个未托管的函数,它将存在于回调函数的生命周期中。托管委托可以在内存中移动,但由封送创建的基础存根不会这样做,因此无需固定委托。然而,需要防止委托被垃圾回收,因为来自未托管代码的指针不足以保持其活动状态。因此,您必须确保通过维护某些托管引用(例如使用GCHandle)来保留委托。

传递给回调函数的PVOID参数必须在内存中固定,因为非托管函数希望在函数返回后它不会移动。在.Net中,这种固定是自动进行的,但仅在被调用的函数的生命周期内。因此,如果您正在使用对某些托管对象的引用(例如通过获取它的IntPtr),则底层对象必须被固定(GCHandle可以以略微不同的方式用于此)。要查看是否存在此问题,请尝试使用IntPtr.Zero作为参数来测试是否有效。
如果这样解决了问题,您需要将参数分配为非托管堆中的原始字节(并相应地进行编组),使用一些可安全放置到PVOID大小的东西的可混合类型(如Int32),或使用上述GCHandle技术来维护指向托管实例的稳定指针,如果错误地执行,这将具有显着的性能影响。

通常情况下,您不必将传递给API函数的委托固定,因为InteropServices会处理这些内容。但是,您确实需要确保您的应用程序保持对该委托的引用,以防止其被垃圾回收。当将来自托管世界的内存块传递到期望东西保留在原地的非托管世界时,固定通常只是必要的。 - MusiGenesis
@MusiGenesis,这就是我所说的“在 .Net 中,几乎任何你传递的东西都很可能发生这种情况”的意思,我可能应该指出这是特定于 PInvoke 的。同样,如果需要,我看不出有必要提到保留引用的需求,因为 P/Invoke 会为您执行此操作:http://msdn.microsoft.com/en-us/23acw07k.aspx - ShuggyCoUk
PInvoke绝对不会为您维护委托引用。编译器将允许您调用CreateTimerQueueTimer并将new TimerCallback(...)作为参数传递。这甚至可以成功运行一段时间,直到您使用new创建的委托恰好被垃圾回收。 - MusiGenesis
@Musi啊,你的意思是如果委托在PInvoke函数返回后被引用作为回调,是的,我会在答案中明确说明这一点。 - ShuggyCoUk
@Shuggy:我必须重申,您不需要使用GCHandle来固定委托以便使用CreateTimerQueueTimer。实际上,您不能使用GCHandle来固定委托。 - MusiGenesis
@Musi 啊是的,PInvoke 通过非托管存根来处理它。我之前不知道这一点。我会添加为什么的链接。 - ShuggyCoUk

2

使用timeSetEvent更好,因为其结果更加一致。在现代硬件上,对于小间隔,时间间隔长度的偏差约为使用CreateTimerQueueTimer时的十倍小。而且这还是假设在调用CreateTimerQueueTimer之前没有忘记增加计时器分辨率的情况下,否则差异会更大。因此,请使用timeSetEvent。


似乎 timeSetEvent 已被弃用。 - Kirk Hawley

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