问题:
为什么在操作系统时钟分辨率更高的情况下,System.Threading.Timer仍保持15毫秒的分辨率?
如何实现1毫秒时间事件分辨率而不需要繁忙的CPU等待?
再次强调:在我的情况下,系统定时器具有1ms分辨率(与问题所示的重复问题不同)。因此,所谓的重复问题中没有有用的信息。
背景: 似乎.NET System.Threading.Timer没有使用系统时钟分辨率,而是保持约15ms的分辨率。尽管操作系统时钟(例如Sleep分辨率)更加精确。
在我的计算机上(几乎空闲且有4个核心可运行):
再次强调:在我的情况下,系统定时器具有1ms分辨率(与问题所示的重复问题不同)。因此,所谓的重复问题中没有有用的信息。
背景: 似乎.NET System.Threading.Timer没有使用系统时钟分辨率,而是保持约15ms的分辨率。尽管操作系统时钟(例如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 <= _minTicksDiff)
{
_minTicksDiff = diff;
return;
}
_casesCount++;
_ticksSum += diff;
}
public void Output()
{
if(_casesCount > 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
{
/// <summary>TimeBeginPeriod(). See the Windows API documentation for details.</summary>
[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);
/// <summary>TimeEndPeriod(). See the Windows API documentation for details.</summary>
[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);
}
编辑1:
环境:
在 .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
不会表现出此行为)。因此,这不是操作系统定时器分辨率(中断频率)的故障 - 这是特定于 System.Threading.Timer
的问题。
编辑2:
(添加了对代码的 TimeBeginPeriod
和 TimeEndPeriod
调用 - 强制更改操作系统定时器分辨率)
System.Threading.Timer
限制在15毫秒内还是个谜。但事实就是这样。我放弃了试图搞清楚原因,只是编写了自己的接口来使用Windows Timer Queue计时器。请参阅使用Windows Timer Queue API。 - Jim Mischel