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

问题描述 投票:57回答:3

请注意,我问的东西,会更频繁地调用回调函数比使用类似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计时器对象。例如,System.Timers.Timer被誉为“用于服务器应用而优化的高性能计时器”。而System.Threading.Timer以某种方式被认为是二等公民。传统的观点是,System.Threading.Timer是围绕Windows Timer Queue Timers的包装和System.Timers.Timer完全是另一回事。

现实的情况是非常不同的。 System.Timers.Timer只是围绕System.Threading.Timer薄部件的包装材料(只需使用反射或ILDASM里面偷看System.Timers.Timer,你会看到参考System.Threading.Timer),并有一些代码,会提供自动的线程同步,这样你就不必这样做。

System.Threading.Timer,事实证明是不适合的定时器队列定时器的包装。至少不会在2.0运行,将其从.NET 2.0通过.NET 3.5使用。与共享源代码CLI几分钟表示运行时间实现自己的定时器队列类似于定时器队列定时器,但实际上从未调用Win32函数。

看来,.NET 4.0运行时也实现了自己的定时器队列。我的测试程序(见下文)在.NET 4.0中提供了类似的结果,因为它确实在.NET 3.5。我已经创建了自己的托管包装的定时器队列定时器和证明,我可以得到1米毫秒的分辨率(与相当不错的准确度),所以我认为这是不可能的,我读的CLI源错误。

我有两个问题:

首先,是什么原因导致运行时的执行定时器队列的是如此之慢?我不能得到优于15毫秒的分辨率和准确性似乎是在-1至+30毫秒范围内。也就是说,如果我要求的24毫秒,我会从23到54毫秒间隔蜱任何地方。我想我可以花更多的时间与CLI源追查答案,但想到这里有人可能知道。

第二,我知道这是很难回答,为什么不使用定时器队列定时器?我知道,.NET 1.x中不得不在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();
        }
    }
}
.net timer
3个回答
28
投票

也许,这里链接的文档解释了它一下。它有点干,所以我只能快速浏览吧:)

引用的介绍:

系统计时器分辨率决定Windows如何频繁执行两个主要行动:

  • 更新计时器滴答计数,如果全剔已过。
  • 检查计划的计时器对象是否已过期。

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

在Windows 7的默认计时器分辨率为15.6毫秒(ms)。一些应用中减少此为1个毫秒,这减少了多达25%的移动通信系统的电池运行时间。

原籍:Timers, Timer Resolution, and Development of Efficient Code (docx).


14
投票

计时器分辨率由系统给出的心跳。这通常默认为64次/秒,其是15.625毫秒。但是有一些方法可以修改这些系统范围的设置,以实现低至1毫秒或在新平台上甚至至0.5毫秒定时器决议:

1.由多媒体计时器接口的手段去为1米毫秒的分辨率:

多媒体计时器接口能够提供低至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毫秒的分辨率:

您可以通过隐藏的API NtSetTimerResolution()的方式获取0.5毫秒的分辨率。 NtSetTimerResolution是由本地的Windows NT库ntdll.dll输出。见How to set timer resolution to 0.5ms ? MSDN上。然而,真正的实现resoltion是由底层硬件决定的。现代硬件不支持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的功能基本上是映射到使用布尔值timeBeginPeriod timeEndPeriodSet(见Inside Windows NT High Resolution Timers有关计划的详情及其所有影响)的功能。然而,多媒体套件限制粒度为毫秒和NtSetTimerResolution允许设置亚毫秒的值。


0
投票

这里所有的录像是关于系统计时器的分辨率。但是,.NET定时器不尊重它。正如作者本人声明:

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

和扬指出在comment

所以,以上的答案是好消息,但不直接与.NET定时器,因此误导人:(

简短的回答,无论是作者的问题是由设计。为什么他们决定走这条路?担心关于整个系统的性能?谁知道... 为了不重复,看到这两个问题(并执行对.NET精密计时器方式)的简的相关topic更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.