我想使用 c# 使用最高分辨率的计时器。例如,我想每 11 个滴答声引发一个事件(我听说滴答声是 pc 中可能的最高计数器)。我尝试了计时器,发现最短经过的时间以毫秒为单位。我查看了秒表,但秒表不会引发事件。
谢谢。
使用多媒体计时器每秒可以为您提供大约 1000 个事件。这段代码应该可以帮助你。
public delegate void TimerEventHandler(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);
/// <summary>
/// A multi media timer with millisecond precision
/// </summary>
/// <param name="msDelay">One event every msDelay milliseconds</param>
/// <param name="msResolution">Timer precision indication (lower value is more precise but resource unfriendly)</param>
/// <param name="handler">delegate to start</param>
/// <param name="userCtx">callBack data </param>
/// <param name="eventType">one event or multiple events</param>
/// <remarks>Dont forget to call timeKillEvent!</remarks>
/// <returns>0 on failure or any other value as a timer id to use for timeKillEvent</returns>
[DllImport("winmm.dll", SetLastError = true,EntryPoint="timeSetEvent")]
static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution, TimerEventHandler handler, ref UInt32 userCtx, UInt32 eventType);
/// <summary>
/// The multi media timer stop function
/// </summary>
/// <param name="uTimerID">timer id from timeSetEvent</param>
/// <remarks>This function stops the timer</remarks>
[DllImport("winmm.dll", SetLastError = true)]
static extern void timeKillEvent( UInt32 uTimerID );
请在运行这些计时器后停止它们。它们在您的系统上非常繁重*。一定要捕获所有异常,不要让它们逃脱您的事件处理程序。
*启动超过 5 个计时器会严重降低大多数系统的速度!在事件处理程序中执行尽可能少的代码,并确保执行代码快于 1 毫秒或面临严重问题。我每 10-50 个刻度开始一个委托以增加标签显示。
在 a 上发生的正常线程切换Thread.Sleep
将使一个线程槽没有代码,大约需要 40 毫秒。您也可以通过一些 NT 内核调用来增加线程切换频率,但请不要这样做。
首先,您需要意识到,由于硬件和软件的限制,在计算机上进行精确计时是非常困难的,如果不是不可能的话。好消息是这种精度很少需要。十个滴答声是非常短的时间。在此间隔内,CPU 完成的工作很少,而且永远不会具有统计意义。
作为参考,Windows 时钟的准确度约为 10 毫秒(在早期版本中更少)。用调用来包装你的代码DateTime.UtcNow
不会比这更好。
在您的问题中,您谈到想要“提出一个事件”。问题在于,以特定间隔引发事件的唯一类型的计时对象是Timer
对象。它在 .NET Framework 中以 3 种不同的形式提供(System.Timers.Timer
、、System.Threading.Timer
和System.Windows.Forms.Timer
),所有这些都有自己独特的使用场景和相关的怪癖,但它们都不能保证接近您所要求的精度。它们甚至不是为此而设计的,而且 Windows API 公开的任何等效函数也不会提供这种类型的精度。
我问您为什么要这样做以及您是否尝试进行基准测试的原因是因为这会改变整个游戏。.NET Framework(从 2.0 版开始)提供了一个Stopwatch
对象,该对象专门用于准确测量基准测试或性能分析等情况的经过时间。Stopwatch
简单地包装了 Windows API 函数QueryPerformanceFrequency
和QueryPerformanceCounter
(这应该证实我对其预期用途的建议)。在早期版本的框架中,我们过去必须 P/Invoke 这些函数才能访问此类功能,但现在它已经很方便地内置了。如果您需要一个具有相对高分辨率的计时器来进行基准测试,那么这Stopwatch
是您的最佳选择。理论上,它可以为您提供亚微秒级的计时。
但这并非没有问题。它不会引发任何事件,因此如果您当前的设计依赖于事件处理,您将不得不重新考虑它。而且,也不能保证完全准确。当然,考虑到硬件限制,它可能具有尽可能高的分辨率,但这并不意味着它一定会满足您规定的要求。例如,它在必须在同一处理器上执行的多处理器系统Start
上可能不可靠。Stop
没关系,但确实如此。在可以上下调节时钟速度的处理器上,它也受到不可靠的影响。我什至敢提QueryPerformanceCounter
本身需要一些时间——即使在现代 2+ GHz 处理器上也需要大约 5 微秒,这使您无法真正实现理论上听起来不错的亚微秒计时。话又说回来,任何合理的代码分析器都会认为该时间量可以忽略不计,因为,嗯,它是.
(另见: http: //www.devsource.com/c/a/Techniques/High-Performance-Timing-under-Windows/2/)
各种计时器类使用更大的粒度。Threading.Timer 和 Timers.Timer 都使用 1/64 秒,即 15.625 毫秒。
如果您所指的“滴答”是 DateTime 类、TimeSpan 类使用并由秒表输出的 100 纳秒滴答,那么您要询问的 11 滴答长度是 1,100 纳秒或 1.1 微秒。据我所知,没有内置计时器可以为您提供该分辨率。如果您确实希望每 1.1 微秒发生一次事件,您将不得不取消“计时器”的想法,而是考虑短暂的延迟。使线程具有高优先级,并在循环中运行您的事件。不要调用 Thread.Sleep(),因为我相信 1.1 微秒小于系统调度程序的时间片。你需要做一个延迟循环。
另外,请意识到您要询问的时间间隔非常非常小。在 2 GHz 处理器上,1.1 微秒仅是 2,200 个处理器周期。不是一个微不足道的数量,但不是很多时间来完成很多工作。如果您说的是您在评论中所说的 1 滴答,那只有 200 个处理器周期:这大约有足够的时间进行几十次数学运算,并且可能调用一个函数。