问题:
尽管操作系统时钟分辨率更精确,为什么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 <= _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);
}
EDIT1:
环境:
在.NET 2.0,3.0,3.5(没有CountDownEvent)和4.5下的Build和Release版本上进行了测试
在Windows 8(Build 9200),Server 2012(Build 9200),Server 2008(Build 6001 SP1)上
Sleep
和Timer
之间存在显着差异。
为什么这不重复:
正如我发布的那样 - 操作系统计时器分辨率设置为1毫秒(并且Sleep
也没有表现出这种行为)。 因此,这不是OS定时器分辨率(中断频率)的错误 - 这是System.Threading.Timer
特有的。
EDIT2:
(添加了TimeBeginPeriod
和TimeEndPeriod
对代码的调用 - 强制更改OS计时器分辨率)
答案 0 :(得分:5)
使用从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))
{
////...
}
尼克
答案 1 :(得分:0)
为什么System.Threading.Timer可以保持15ms的分辨率,而OS时钟的分辨率却要精确得多?
显然是由于实施。 System.Threading.Timer(因此Task.Delay)使用.NET运行时计时器队列,该队列不遵守系统计时器的分辨率。此外,我运行了(.net 4.x)Windows(7、10;服务器2012、2016)测试,发现WaitHandle.WaitOne()和Monitor.Wait()不尊重 WinForms上的系统计时器解析度GUI线程((answer above使用WaitHandle)。因此,只有Thread.Sleep在GUI线程上尊重它。
在不忙于等待CPU的情况下实现1ms定时事件分辨率的推荐方法是什么?
Jim Mischel指出的一种方法。但是,它有以下缺点:
在 windows 线程池线程上执行回调。
时间间隔与当前时间相对。
时间间隔为整数 ms,因此理论上最大精度为1 ms。
根据许多报告, 1.5-2毫秒精度几乎是您可以达到的,并且只能通过timeBeginPeriod(1)来 。
另一种方法是:NtSetTimerResolution和Waitable Timer Objects。
您可以获得0.5毫秒的分辨率(取决于硬件和Windows版本)。
对于c#示例(这不是您的计时器类的示例,而是在c#中使用此函数的示例),您可以选中此article。
您也可以尝试Nick's的建议,但要记住GUI线程的问题。