请注意,我要问的是,使用类似的东西,每隔15毫秒就会调用一次回调函数System.Threading.Timer.我不是在询问如何使用类似System.Diagnostics.Stopwatch甚至是类似的东西准确计算代码QueryPerformanceCounter.
另外,我已经阅读了相关问题:
准确的Windows计时器?System.Timers.Timer()限制为15毫秒
这些都没有为我的问题提供有用的答案.
此外,推荐的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事实证明,它不是 Timer Queue Timers的包装器.至少不在2.0运行时,它是从.NET 2.0到.NET 3.5使用的.使用Shared Source CLI几分钟后,运行时会实现自己的定时器队列,类似于Timer Queue Timers,但实际上从不调用Win32函数.
似乎.NET 4.0运行时还实现了自己的计时器队列.我的测试程序(见下文)在.NET 4.0下提供与.NET 3.5相似的结果.我已经为Timer Queue Timers创建了自己的托管包装器并证明我可以获得1 ms的分辨率(具有相当好的准确性),所以我认为我不太可能错误地读取CLI源代码.
我有两个问题:
首先,是什么原因导致运行时计时器队列的实现如此缓慢?我的分辨率不能超过15毫秒,精度似乎在-1到+30毫秒的范围内.也就是说,如果我要求24毫秒,我会在23到54毫秒之间的任何地方得到滴答声.我想我可以花更多时间使用CLI源来追踪答案,但我想这里有人可能知道.
其次,我意识到这很难回答,为什么不使用定时器队列定时器?我意识到.NET 1.x必须在没有这些API的Win9x上运行,但它们自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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
Arn*_*nce 28
也许这里链接的文件解释了一下.它有点干,所以我很快就浏览了:)
引用介绍:
系统计时器分辨率决定Windows执行两个主要操作的频率:
- 如果已经过了完整的滴答,则更新计时器滴答计数.
- 检查计划的计时器对象是否已过期.
计时器刻度是Windows用于跟踪时间和线程量子时间的经过时间的概念.默认情况下,时钟中断和计时器滴答相同,但Windows或应用程序可以更改时钟中断周期.
Windows 7上的默认计时器分辨率为15.6毫秒(ms).某些应用程序将此时间缩短至1 ms,从而将移动系统上的电池运行时间缩短了25%.
Arn*_*rno 15
计时器分辨率由系统心跳给出.这通常默认为64次/秒,即15.625毫秒.但是,有些方法可以修改这些系统范围的设置,以便在较新的平台上实现低至1毫秒甚至0.5毫秒的定时器分辨率:
1.通过多媒体计时器界面获得1 ms分辨率:
多媒体定时器接口能够提供低至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);
Run Code Online (Sandbox Code Playgroud)
注意:此过程也适用于其他过程,并且所获得的分辨率适用于整个系统.任何流程要求的最高解决方案都将是活跃的,请注意后果.
2.分辨率为0.5毫秒:
您可以通过隐藏的API 获得0.5 ms的分辨率NtSetTimerResolution().NtSetTimerResolution由本机Windows NT库NTDLL.DLL导出.请参阅如何将计时器分辨率设置为0.5毫秒?在MSDN上.然而,真正可实现的解决方案由底层硬件决定.现代硬件支持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
}
Run Code Online (Sandbox Code Playgroud)
注意:NtSetTImerResolution的功能基本上映射到函数timeBeginPeriod 并 timeEndPeriod使用bool值Set(有关该方案及其所有含义的更多详细信息,请参阅内部Windows NT高分辨率计时器).但是,多媒体套件将粒度限制为毫秒,NtSetTimerResolution允许设置亚毫秒值.
| 归档时间: |
|
| 查看次数: |
29504 次 |
| 最近记录: |