代码之家  ›  专栏  ›  技术社区  ›  Jim Mischel

为什么.NET计时器的分辨率限制为15毫秒?

  •  57
  • Jim Mischel  · 技术社区  · 14 年前

    请注意,我询问的是一个调用回调函数的方法,该方法使用类似的方法每15毫秒调用一次以上 System.Threading.Timer . 我不是在问如何使用类似于 System.Diagnostics.Stopwatch 甚至 QueryPerformanceCounter .

    此外,我还阅读了相关问题:

    Accurate Windows timer? System.Timers.Timer() is limited to 15 msec

    High resolution timer in .NET

    这两者都不能回答我的问题。

    此外,推荐的msdn文章, Implement a Continuously Updating, High-Resolution Time Provider for Windows ,是关于计时的事情,而不是提供连续的滴答声流。

    就这么说。…

    有很多关于.NET计时器对象的坏信息。例如, System.Timers.Timer 被称为“针对服务器应用程序优化的高性能计时器”。 系统.线程.计时器 被认为是二等公民。传统的智慧是 系统.线程.计时器 是一个包裹在窗户周围的东西 Timer Queue Timers 系统计时器计时器 完全是另一回事。

    现实情况大相径庭。 系统计时器计时器 只是一个很薄的组件包装 系统.线程.计时器 (只需使用反射镜或ILDASM窥视内部 系统计时器计时器 你会看到 系统.线程.计时器 ,并且有一些代码可以提供自动线程同步,因此您不必这样做。

    系统.线程.计时器 事实证明 不是 计时器队列计时器的包装。至少在从.NET 2.0到.NET 3.5使用的2.0运行时中没有。使用共享源CLI的几分钟时间表明,运行时实现了自己的计时器队列,该队列与计时器队列计时器类似,但从未实际调用win32函数。

    .NET 4.0运行时似乎也实现了自己的计时器队列。我的测试程序(见下文)在.NET 4.0下提供了与在.NET 3.5下类似的结果。我已经为计时器队列计时器创建了自己的托管包装器,并证明了我可以获得1毫秒的分辨率(具有相当好的准确性),因此我认为我不太可能错误地读取CLI源。

    我有两个问题:

    首先,是什么导致运行时对计时器队列的实现如此缓慢?我无法获得超过15毫秒的分辨率,而且精度似乎在-1到+30毫秒的范围内。也就是说,如果我要求24毫秒,我将得到从23到54毫秒的任何间隔。我想我可以花更多的时间使用cli源代码来跟踪答案,但我想这里的人可能知道。

    其次,我意识到这很难回答,为什么不使用计时器队列计时器呢?我意识到.NET 1.x必须在Win9x上运行,Win9x没有这些API,但它们自Windows 2000以来就存在了,如果我记得正确的话,这是.NET 2.0的最低要求。是因为cli必须在非Windows设备上运行吗?

    我的计时器测试程序:

    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();
            }
        }
    }
    
    3 回复  |  直到 6 年前
        1
  •  28
  •   vgru    10 年前

    Windows 7的分辨率为15.6 毫秒(ms)。一些应用程序 将此值减少到1_ms,这会减少 移动系统电池运行时间 高达25%。

    最初来源: Timers, Timer Resolution, and Development of Efficient Code (docx).

        2
  •  14
  •   Community Marino Di Clemente    7 年前

    计时器分辨率由系统心跳给出。这通常默认为64拍/秒,即15.625毫秒。但是,有一些方法可以修改这些系统范围的设置,以在较新的平台上将计时器分辨率降低到1毫秒甚至0.5毫秒:

    1。通过多媒体计时器接口进行1 ms分辨率:

    多媒体计时器接口可提供低至1毫秒的分辨率。 见 About Multimedia Timers (MSDN) Obtaining and Setting Timer Resolution (MSDN),以及 this 请回答有关的详细信息 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); 
    

    注:此程序也适用于其他过程,获得的分辨率适用于整个系统。任何流程要求的最高解决方案都将是主动的,注意后果。

    2。转到0.5 ms分辨率:

    可获得0.5 ms 通过隐藏的API解析 NtSetTimerResolution() . ntsettimerresolution由本机Windows NT库nt dll.dll导出。见 How to set timer resolution to 0.5ms ? 在MSDN上。然而,真正可实现的解决方案是由底层硬件决定的。现代硬件支持0.5毫秒的分辨率。 更多详细信息请参见 Inside Windows NT High Resolution Timers . 通过调用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的功能基本上映射到函数 时间开始阶段 timeEndPeriod 通过使用bool值 Set (见 内置Windows NT高分辨率计时器 有关该计划及其所有影响的更多详细信息)。但是,多媒体套件将粒度限制为毫秒,而ntsettimerresolution允许设置亚毫秒的值。

        3
  •  0
  •   Kirsan    6 年前

    这里的所有重播都是关于系统计时器分辨率的。 但是 NET计时器 尊重它。作者本人注意到:

    运行时实现自己的计时器队列,类似于 计时器队列计时器,但从未实际调用win32函数。

    简指出 comment .

    因此,上面的答案是很好的信息,但与.NET计时器没有直接关联,因此误导了人们:(

    两个作者问题的简短答案是 按设计 . 他们为什么决定走这条路?担心整个系统性能?谁知道…
    要避免重复,请参阅有关这两个问题(以及实现精确性的方法)的详细信息 .net上的计时器)与jan相关 topic .