为什么.NET定时器限制在15毫秒的分辨率?
请注意,我正在询问一些使用类似System.Threading.Timer
每15毫秒会更频繁地调用一次callback函数。 我不问如何使用像System.Diagnostics.Stopwatch
甚至QueryPerformanceCounter
一样准确地计时一段代码。
另外,我已经阅读了相关的问题:
准确的Windows计时器? System.Timers.Timer()被限制为15毫秒
.NET中的高分辨率计时器
这两者都不能为我的问题提供有用的答案。
此外,推荐的MSDN文章“ 实现持续更新,Windows的高分辨率时间提供程序”是关于计时的事情,而不是提供连续的滴答。
说着。 。 。
.NET定时器对象有很多不好的信息。 例如, System.Timers.Timer
被称为“为服务器应用程序优化的高性能计时器”。 而System.Threading.Timer
在某种程度上被认为是二等公民。 传统的观点是System.Threading.Timer
是Windows 定时器队列定时器的一个包装, System.Timers.Timer
是完全不同的东西。
现实是非常不同的。 System.Timers.Timer
只是System.Threading.Timer
(只使用Reflector或ILDASM来查看System.Timers.Timer
内部,你会看到对System.Threading.Timer
的引用)的瘦组件包装,并且有一些代码这将提供自动的线程同步,所以你不必这样做。
System.Threading.Timer
,事实certificate它不是定时器队列定时器的包装器。 至less不在2.0运行时,从.NET 2.0到.NET 3.5使用。 几分钟的共享源CLI显示运行时实现了与定时器队列计时器类似的自己的计时器队列,但从未实际调用Win32函数。
看来,.NET 4.0运行时也实现了它自己的计时器队列。 我的testing程序(见下)在.NET 4.0下提供了类似的结果,就像在.NET 3.5下一样。 我创build了自己的定时器队列定时器的托pipe包装,并certificate我可以获得1毫秒的分辨率(具有相当高的准确性),所以我认为我不太可能读取CLI源错误。
我有两个问题:
首先,什么导致定时器队列的运行时执行如此缓慢? 我不能超过15毫秒的分辨率,准确度似乎在-1到+30毫秒的范围内。 也就是说,如果我要求24毫秒,我会在23到54毫秒之间的任何地方得到滴答。 我想我可以花更多的时间用CLI来追踪答案,但是这里有人可能知道。
其次,我意识到这很难回答,为什么不使用定时器队列定时器呢? 我意识到.NET 1.x必须在Win9x上运行,它没有这些API,但是从Windows 2000开始就已经存在了,如果我没有记错的话,它是.NET 2.0的最低要求。 是否因为CLI必须在非Windows盒子上运行?
我的计时器testing程序:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace TimerTest { class Program { const int TickFrequency = 5; const int TestDuration = 15000; // 15 seconds static void Main(string[] args) { // Create a list to hold the tick times // The list is pre-allocated to prevent list resizing // from slowing down the test. List<double> tickTimes = new List<double>(2 * TestDuration / TickFrequency); // Start a stopwatch so we can keep track of how long this takes. Stopwatch Elapsed = Stopwatch.StartNew(); // Create a timer that saves the elapsed time at each tick Timer ticker = new Timer((s) => { tickTimes.Add(Elapsed.ElapsedMilliseconds); }, null, 0, TickFrequency); // Wait for the test to complete Thread.Sleep(TestDuration); // Destroy the timer and stop the stopwatch ticker.Dispose(); Elapsed.Stop(); // Now let's analyze the results Console.WriteLine("{0:N0} ticks in {1:N0} milliseconds", tickTimes.Count, Elapsed.ElapsedMilliseconds); Console.WriteLine("Average tick frequency = {0:N2} ms", (double)Elapsed.ElapsedMilliseconds / tickTimes.Count); // Compute min and max deviation from requested frequency double minDiff = double.MaxValue; double maxDiff = double.MinValue; for (int i = 1; i < tickTimes.Count; ++i) { double diff = (tickTimes[i] - tickTimes[i - 1]) - TickFrequency; minDiff = Math.Min(diff, minDiff); maxDiff = Math.Max(diff, maxDiff); } Console.WriteLine("min diff = {0:N4} ms", minDiff); Console.WriteLine("max diff = {0:N4} ms", maxDiff); Console.WriteLine("Test complete. Press Enter."); Console.ReadLine(); } } }
也许这里链接的文件解释了一下。 这有点干,所以我只浏览它:)
引用介绍:
系统计时器分辨率决定了Windows执行两个主要操作的频率:
- 如果满刻度已经过去,更新计时器滴答计数。
- 检查计划的计时器对象是否已过期。
计时器刻度是Windows用来跟踪一天中的时间和线程量时间的经过时间的概念。 默认情况下,时钟中断和计时器滴答是一样的,但Windows或应用程序可以改变时钟中断周期。
Windows 7上的默认计时器分辨率是15.6毫秒(ms)。 某些应用程序将其减less到1 ms,这将移动系统上的电池运行时间缩短了25%。
最初来自: 定时器,计时器分辨率和高效代码(docx)的开发。
定时器分辨率由系统心跳给出。 这通常默认为64次/秒,即15.625毫秒。 但是,有些方法可以修改这些系统范围的设置,以在较新的平台上将计时器分辨率降低到1毫秒甚至0.5毫秒。
1.通过多媒体定时器接口以1毫秒的分辨率进行:
多媒体计时器接口能够提供低至1毫秒的分辨率。 请参阅关于多媒体计时器 (MSDN), 获取和设置计时器分辨率 (MSDN)和此答案以获取有关timeBeginPeriod
更多详细信息。 注意:不要忘记在完成时调用timeEndPeriod切换回默认的定时器分辨率。
怎么做:
#define TARGET_RESOLUTION 1 // 1-millisecond target resolution TIMECAPS tc; UINT wTimerRes; if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) { // Error; application can't continue. } wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax); timeBeginPeriod(wTimerRes); // do your stuff here at approx. 1 ms timer resolution timeEndPeriod(wTimerRes);
注意:此过程也适用于其他过程,所获得的parsing适用于系统范围。 任何进程所要求的最高决议都将是积极的,请注意后果。
2.以0.5毫秒的分辨率:
您可以通过隐藏的API NtSetTimerResolution()
获得0.5毫秒的分辨率。 NtSetTimerResolution由本机Windows NT库NTDLL.DLL导出。 请参阅如何将定时器分辨率设置为0.5ms? 在MSDN上。 不过,真正可以实现的解决scheme是由底层硬件决定的。 现代硬件支持0.5毫秒的分辨率。 Inside Windows NT高分辨率定时器中还有更多的细节。 支持的分辨率可以通过调用NtQueryTimerResolution()来获得。
怎么做:
#define STATUS_SUCCESS 0 #define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245 // after loading NtSetTimerResolution from ntdll.dll: // The requested resolution in 100 ns units: ULONG DesiredResolution = 5000; // Note: The supported resolutions can be obtained by a call to NtQueryTimerResolution() ULONG CurrentResolution = 0; // 1. Requesting a higher resolution // Note: This call is similar to timeBeginPeriod. // However, it to to specify the resolution in 100 ns units. if (NtSetTimerResolution(DesiredResolution ,TRUE,&CurrentResolution) != STATUS_SUCCESS) { // The call has failed } printf("CurrentResolution [100 ns units]: %d\n",CurrentResolution); // this will show 5000 on more modern platforms (0.5ms!) // do your stuff here at 0.5 ms timer resolution // 2. Releasing the requested resolution // Note: This call is similar to timeEndPeriod switch (NtSetTimerResolution(DesiredResolution ,FALSE,&CurrentResolution) { case STATUS_SUCCESS: printf("The current resolution has returned to %d [100 ns units]\n",CurrentResolution); break; case STATUS_TIMER_RESOLUTION_NOT_SET: printf("The requested resolution was not set\n"); // the resolution can only return to a previous value by means of FALSE // when the current resolution was set by this application break; default: // The call has failed }
注意:NtSetTImerResolution的function基本上是通过使用布尔值Set
(请参阅Inside Windows NT高分辨率定时器了解有关该scheme及其所有含义的更多详细信息)映射到函数timeBeginPeriod
和 timeEndPeriod
。 但是,多媒体套件将粒度限制为毫秒,NtSetTimerResolution允许设置亚毫秒值。
作为一个定时器,根据上下文切换的时间(例如但不限于networking堆栈中的计数器),这可靠地下降到1μs。
见http://www.codeproject.com/Articles/571289/Obtaining-Microsecond-Precision-in-NET
它比以前看到的任何东西都less得多,并且胜过性能计数器!
您的操作系统或networking堆栈或底层的软件驱动程序可能有其他此类警告影响此机制的准确性; 然而,由于这种精确度可以进一步根据诸如但不限于温度的因素被分成可接受的和不可接受的容差,所以根据环境和前述项目,进一步利用这样的数值来确定此时的最佳反应。