请注意,我要问的是,使用类似的东西,每隔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 次 |
最近记录: |