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

为什么.NET计时器的分辨率限制为15毫秒?,.net,timer,.net,Timer,请注意,我询问的是使用类似于System.Threading.Timer的方法调用回调函数的频率会超过每15毫秒一次。我不是在问如何使用诸如System.Diagnostics.Stopwatch或甚至QueryPerformanceCounter之类的工具来精确计时一段代码 此外,我还阅读了相关问题: 这两个都不能为我的问题提供有用的答案 此外,推荐的MSDN文章是关于计时的,而不是提供连续的滴答声流 话虽如此 关于.NET计时器对象有很多不好的信息。例如,System.Timers.Ti

请注意,我询问的是使用类似于
System.Threading.Timer
的方法调用回调函数的频率会超过每15毫秒一次。我不是在问如何使用诸如
System.Diagnostics.Stopwatch
或甚至
QueryPerformanceCounter
之类的工具来精确计时一段代码

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

这两个都不能为我的问题提供有用的答案

此外,推荐的MSDN文章是关于计时的,而不是提供连续的滴答声流

话虽如此

关于.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
,事实证明它不是计时器队列计时器的包装器。至少不是在2.0运行时中,它是从.NET2.0到.NET3.5使用的。使用共享源CLI的几分钟时间表明,运行时实现了自己的计时器队列,与计时器队列计时器类似,但从未实际调用Win32函数

NET 4.0运行时似乎也实现了自己的计时器队列。我的测试程序(见下文)在.NET4.0下提供了与在.NET3.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();
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用系统线程;
名称空间时间测试
{
班级计划
{
常数int TickFrequency=5;
const int TestDuration=15000;//15秒
静态void Main(字符串[]参数)
{
//创建一个列表以保存勾号时间
//该列表是预先分配的,以防止调整列表大小
//避免减慢测试速度。
列表计时=新列表(2*测试持续时间/计时频率);
//启动秒表,这样我们就可以记录这需要多长时间。
秒表运行时间=Stopwatch.StartNew();
//创建一个计时器,用于保存每次滴答声时经过的时间
定时器计时器=新定时器((s)=>
{
tickTimes.Add(已用时间.elapsedmillyses);
},空,0,频率);
//等待测试完成
睡眠(TestDuration);
//破坏计时器并停止秒表
Dispose();
停止();
//现在让我们分析一下结果
WriteLine({1:N0}毫秒内的{0:N0}个滴答声),tickTimes.Count,appead.elapsedmillesons);
WriteLine(“平均滴答频率={0:N2}ms”,(double)persed.elapsedmillesons/tickTimes.Count);
//计算与请求频率的最小和最大偏差
double minDiff=double.MaxValue;
double maxDiff=double.MinValue;
对于(int i=1;i
也许这里链接的文档对此做了一些解释。有点干,所以我只是快速浏览了一下:)

引述介绍:

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

  • 更新计时器刻度 如果已满刻度,则计数
  • 检查是否存在定时计时器对象 已经过期了
计时器滴答声是已过时间的概念 Windows用于跟踪数据的时间 一天中的时间和线程量子时间。 默认情况下,时钟中断和 计时器滴答声是相同的,但窗口 或者应用程序可以更改时钟 中断时间

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

原为f
#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); 
#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

}