System.Threading.Timer vs System.Threading.Thread.Sleep resolution – .NET Timer不使用系统时钟分辨率

问题:尽管OS时钟分辨率更精确,为什么System.Threading.Timer保持15ms的分辨率? 在没有繁忙的CPU等待的情况下,实现1ms定时事件解析的可行方法是什么?

再次强调: 在我的情况下,系统计时器的分辨率为1ms (与建议重复的问题相反)。 所以这不是系统计时器分辨率的问题。 因此,在所谓的重复问题中没有有用的信息。

背景:似乎.NET System.Threading.Timer 没有使用系统时钟分辨率 – 它保持了~15ms的分辨率。 尽管OS时钟(例如Sleep分辨率)更加精确。

在我的盒子上(当几乎空闲并且有4个核心可用时):

 >Clockres.exe ClockRes v2.0 - View the system clock resolution Copyright (C) 2009 Mark Russinovich SysInternals - www.sysinternals.com Maximum timer interval: 15.625 ms Minimum timer interval: 0.500 ms Current timer interval: 1.001 ms 

输出我的快速测试:

 Sleep test: Average time delta: 2[ms] (from 993 cases) System.Threading.Timer test: Average time delta: 15[ms] (from 985 cases) 

测试代码是:

 private static void TestSleepVsTimer(long millisecondsDifference, int repetions) { TimingEventsKeeper timingEventsKeeper = new TimingEventsKeeper(); timingEventsKeeper.Reset((int) millisecondsDifference, repetions); while (!timingEventsKeeper.TestDoneEvent.IsSet) { timingEventsKeeper.CountNextEvent(null); Thread.Sleep((int) millisecondsDifference); } Console.WriteLine("Sleep test: "); timingEventsKeeper.Output(); timingEventsKeeper.Reset((int) millisecondsDifference, repetions); Timer t = new Timer(timingEventsKeeper.CountNextEvent, null, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(1)); timingEventsKeeper.TestDoneEvent.Wait(); Console.WriteLine("System.Threading.Timer test: "); timingEventsKeeper.Output(); } private class TimingEventsKeeper { long _ticksSum = 0; long _casesCount = 0; long _minTicksDiff; long _maxTicksDiff; long _lastTicksCount; int _repetitons; public CountdownEvent TestDoneEvent = new CountdownEvent(0); public void Reset(int millisecondsDifference, int repetitions) { _ticksSum = 0; _casesCount = 0; _minTicksDiff = millisecondsDifference * 10000; _maxTicksDiff = millisecondsDifference * 10000; _lastTicksCount = DateTime.UtcNow.Ticks; _repetitons = repetitions; TestDoneEvent.Reset(repetitions); } public void CountNextEvent(object unused) { long currTicksCount = DateTime.UtcNow.Ticks; long diff = currTicksCount - _lastTicksCount; _lastTicksCount = currTicksCount; TestDoneEvent.Signal(); if (diff >= _maxTicksDiff) { _maxTicksDiff = diff; return; } if (diff  0) Console.WriteLine("Average time delta: {0}[ms] (from {1} cases)", _ticksSum / _casesCount / 10000, _casesCount); else Console.WriteLine("No measured cases to calculate average"); } } public static class WinApi { /// TimeBeginPeriod(). See the Windows API documentation for details. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity] [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)] public static extern uint TimeBeginPeriod(uint uMilliseconds); /// TimeEndPeriod(). See the Windows API documentation for details. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity] [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)] public static extern uint TimeEndPeriod(uint uMilliseconds); } private static void Main(string[] args) { WinApi.TimeBeginPeriod(1); TestSleepVsTimer(1, 1000); WinApi.TimeEndPeriod(1); } 

EDIT1:

环境:在.NET 2.0,3.0,3.5(没有CountDownEvent)和4.5在Windows 8(Build 9200),Server 2012(Build 9200),Server 2008(Build 6001 SP1)下的构建和发布版本上进行测试,在Sleep之间存在显着差异和Timer

为什么这不重复:正如我发布的那样 – 操作系统定时器分辨率设置为1ms(并且Sleep也没有表现出这种行为)。 因此,这不是OS定时器分辨率(中断频率)的错误 – 这是System.Threading.Timer特有的。

EDIT2 🙁添加了TimeBeginPeriodTimeEndPeriod调用代码 – 强制OS定时器分辨率更改)

尽管OS时钟分辨率更精确,为什么System.Threading.Timer保持15ms的分辨率?

显然是由于实施。 System.Threading.Timer(以及Task.Delay)使用.NET运行时计时器队列,不考虑系统计时器分辨率。 此外,我运行测试(.net 4.x)windows(7,10;服务器2012年,2016年)并发现,WaitHandle.WaitOne()和Monitor.Wait()也不尊重系统计时器分辨率(这是答案)以上使用WaitHandle)。 那么,只有Thread.Sleep尊重它吗? (是的,我没有测试Semaphore和Mutex)。

在没有繁忙的CPU等待的情况下,实现1ms定时事件解析的可行方法是什么?

Jim Mischel指出的一种方式。 但是,它有一些缺点,如:
Windows线程池线程上执行回调。
时间间隔是对于当前时间的。
时间间隔是整数 ms,因此理论上最大精度为1 ms。
通过许多报告, 只有使用timeBeginPeriod(1)调用, 1.5-2 ms的精度几乎是你可以实现的最大值。

另一种方法是: NtSetTimerResolution和Waitable Timer Objects 。 您可以获得0.5毫秒的分辨率(取决于硬件和Windows版本)。
对于c#示例(它不是您的计时器类的示例,而是在c#中使用此函数的示例),您可以查看本文 。

使用从WaitHandle派生的其中一个同步类,例如AutoResetEvent或ManualResetEvent,在调用WaitOne()方法时设置timeout参数。

通过循环调用WaitOne,您可以实现计时器。

您可以发出等待句柄派生类的信号以断开或中断计时器。

注意,要更改分辨率,最好使用实现IDisposable的帮助程序类:

 internal sealed class TimePeriod : IDisposable { private const string WINMM = "winmm.dll"; private static TIMECAPS timeCapabilities; private static int inTimePeriod; private readonly int period; private int disposed; [DllImport(WINMM, ExactSpelling = true)] private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc); [DllImport(WINMM, ExactSpelling = true)] private static extern int timeBeginPeriod(int uPeriod); [DllImport(WINMM, ExactSpelling = true)] private static extern int timeEndPeriod(int uPeriod); static TimePeriod() { int result = timeGetDevCaps(ref timeCapabilities, Marshal.SizeOf(typeof(TIMECAPS))); if (result != 0) { throw new InvalidOperationException("The request to get time capabilities was not completed because an unexpected error with code " + result + " occured."); } } internal TimePeriod(int period) { if (Interlocked.Increment(ref inTimePeriod) != 1) { Interlocked.Decrement(ref inTimePeriod); throw new NotSupportedException("The process is already within a time period. Nested time periods are not supported."); } if (period < timeCapabilities.wPeriodMin || period > timeCapabilities.wPeriodMax) { throw new ArgumentOutOfRangeException("period", "The request to begin a time period was not completed because the resolution specified is out of range."); } int result = timeBeginPeriod(period); if (result != 0) { throw new InvalidOperationException("The request to begin a time period was not completed because an unexpected error with code " + result + " occured."); } this.period = period; } internal static int MinimumPeriod { get { return timeCapabilities.wPeriodMin; } } internal static int MaximumPeriod { get { return timeCapabilities.wPeriodMax; } } internal int Period { get { if (this.disposed > 0) { throw new ObjectDisposedException("The time period instance has been disposed."); } return this.period; } } public void Dispose() { if (Interlocked.Increment(ref this.disposed) == 1) { timeEndPeriod(this.period); Interlocked.Decrement(ref inTimePeriod); } else { Interlocked.Decrement(ref this.disposed); } } [StructLayout(LayoutKind.Sequential)] private struct TIMECAPS { internal int wPeriodMin; internal int wPeriodMax; } } 

然后你可以使用:

 using (new TimePeriod(1)) { ////... } 

缺口