![]() |
狂野的啄木鸟 · Python中节省内存的方法之二:弱引用we ...· 1 年前 · |
![]() |
会开车的仙人掌 · 【python】聊聊容易被忽视的“代码规范” ...· 1 年前 · |
![]() |
坏坏的麦片 · 解决WCF大数据量传输 ...· 1 年前 · |
![]() |
打盹的脆皮肠 · 解决:target\surefire-rep ...· 2 年前 · |
请注意,我问的是使用
System.Threading.Timer
之类的东西调用回调函数的频率超过每15毫秒一次的情况。我并不是在问如何使用诸如
System.Diagnostics.Stopwatch
甚至
QueryPerformanceCounter
之类的东西来精确地对一段代码进行计时。
此外,我还阅读了相关问题:
Accurate Windows timer? System.Timers.Timer() is limited to 15 msec
这两个都没有对我的问题提供有用的答案。
此外,推荐的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机器上运行?
我的定时器测试程序:
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();
}
发布于 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).
发布于 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 来切换回默认的计时器分辨率。
如何做:
#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()获得。
如何做:
#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