首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么.NET计时器限制为15毫秒的分辨率?

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

Stack Overflow用户
提问于 2010-09-19 08:12:40
回答 3查看 34.5K关注 0票数 66

请注意,我问的是使用System.Threading.Timer之类的东西调用回调函数的频率超过每15毫秒一次的情况。我并不是在问如何使用诸如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 timer对象的坏信息。例如,System.Timers.Timer被标榜为“为服务器应用程序优化的高性能计时器”。不知何故,System.Threading.Timer被认为是二等公民。传统观点认为System.Threading.Timer是Windows Timer Queue Timers的包装器,而System.Timers.Timer则完全是另外一回事。

现实情况则大相径庭。System.Timers.Timer只是System.Threading.Timer的一个薄薄的组件包装器(只需使用Reflector或ILDASM来窥探System.Timers.Timer内部,您就会看到对System.Threading.Timer的引用),并且有一些代码将提供自动线程同步,因此您不必这么做。

事实证明,System.Threading.Timer并不是定时器队列定时器的包装器。至少在从.NET 2.0到.NET 3.5的2.0运行时中不是这样。几分钟的共享源命令行界面显示,运行时实现了自己的计时器队列,这与计时器队列计时器类似,但从未实际调用过Win32函数。

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

我有两个问题:

首先,是什么导致运行时对计时器队列的实现如此缓慢?我不能获得超过15毫秒的分辨率,精度似乎在-1到+30毫秒的范围内。也就是说,如果我请求24毫秒,我会得到23到54毫秒的间隔。我想我可以花更多的时间与CLI源代码一起追查答案,但我想这里可能会有人知道。

其次,我意识到这很难回答,为什么不使用计时器队列计时器呢?我意识到Windows1.x必须在没有这些API的Win9x上运行,但它们从Windows2000开始就存在了,如果我没记错的话,这是.NET 2.0的最低要求。是否因为CLI必须在非Windows机器上运行?

我的定时器测试程序:

代码语言:javascript
复制
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();
        }
    }
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-09-19 08:55:16

也许这里链接的文档对此做了一些解释。它有点干燥,所以我只是快速浏览了一下:)

引述引言:

系统计时器分辨率决定了Windows执行两个主要操作的频率:

  • 如果已过满时间,则更新计时器计时计数。
  • 检查计划的计时器对象是否已过期。

计时器滴答是一个经过时间的概念,Windows使用它来跟踪一天中的时间和线程量程时间。默认情况下,时钟中断和定时器滴答相同,但Windows或应用程序可以更改时钟中断周期。

Windows 7上的默认计时器分辨率为15.6毫秒(ms)。一些应用程序将这一时间减少到1毫秒,从而将移动系统上的电池运行时间减少了多达25%。

出自:Timers, Timer Resolution, and Development of Efficient Code (docx).

票数 29
EN

Stack Overflow用户

发布于 2014-04-04 20:35:04

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

1.通过多媒体计时器接口实现1毫秒的分辨率:

多媒体定时器接口能够提供低至1毫秒的分辨率。有关this的更多详细信息,请参阅About Multimedia Timers (MSDN)、Obtaining and Setting Timer Resolution (MSDN)和this answer。注意:完成后,不要忘记调用timeEndPeriod来切换回默认的计时器分辨率。

如何做:

代码语言:javascript
复制
#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毫秒分辨率:

您的可以通过隐藏接口NtSetTimerResolution()获得0.5ms的分辨率。NtSetTimerResolution由本机Windows NT库NTDLL.DLL导出。请参阅MSDN上的How to set timer resolution to 0.5ms ?。然而,真正可实现的解决方案取决于底层硬件。现代硬件支持0.5毫秒的分辨率。更多细节可以在Inside Windows NT High Resolution Timers中找到。支持的分辨率可以通过调用NtQueryTimerResolution()获得。

如何做:

代码语言:javascript
复制
#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的功能基本上通过使用布尔值Set映射到函数timeBeginPeriod timeEndPeriod (有关该方案及其所有含义的更多详细信息,请参阅Inside Windows NT High Resolution Timers )。但是,多媒体套件将粒度限制为毫秒,而NtSetTimerResolution允许设置亚毫秒级的值。

票数 23
EN

Stack Overflow用户

发布于 2018-09-27 18:12:01

这里的所有回放都是关于系统计时器分辨率的。但是 .net timers 不是尊重它。正如作者自己所注意到的:

运行时实现其自己的计时器队列的

,该计时器队列与计时器队列计时器类似,但从不实际调用Win32函数。

简指的是comment

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

这两个作者问题的简短答案都是设计出来的。他们为什么决定走这条路?担心整个系统的性能吗?谁知道呢。

为了不重复,请参阅Jan的correlated topic中有关这两个问题的更多信息(以及在.net上实现精确计时器的方法)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3744032

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档