C# System.Threading.Timer vs System.Threading.Thread.Sleep resolution-.NET计时器未使用系统时钟分辨率
问题: 为什么C# System.Threading.Timer vs System.Threading.Thread.Sleep resolution-.NET计时器未使用系统时钟分辨率,c#,.net,multithreading,timer,scheduling,C#,.net,Multithreading,Timer,Scheduling,问题: 为什么System.Threading.Timer在操作系统时钟分辨率更精确的情况下仍保持15毫秒的分辨率? 在没有繁忙的CPU等待的情况下,实现1ms定时事件分辨率的推荐方法是什么 再次强调:系统计时器在我的情况下分辨率为1ms(与重复的问题相反)。因此这不是系统计时器分辨率的问题。因此,假设重复的问题中没有有用的信息 背景: 似乎.NETSystem.Threading.Timer没有使用系统时钟分辨率-它保持~15ms的分辨率。尽管操作系统时钟(例如睡眠分辨率)更精确 在我的机箱上
System.Threading.Timer
在操作系统时钟分辨率更精确的情况下仍保持15毫秒的分辨率?
在没有繁忙的CPU等待的情况下,实现1ms定时事件分辨率的推荐方法是什么
再次强调:系统计时器在我的情况下分辨率为1ms(与重复的问题相反)。因此这不是系统计时器分辨率的问题。因此,假设重复的问题中没有有用的信息
背景:
似乎.NETSystem.Threading.Timer
没有使用系统时钟分辨率-它保持~15ms的分辨率。尽管操作系统时钟(例如睡眠
分辨率)更精确
在我的机箱上(几乎空闲且有4个内核可供运行时):
我的快速测试的输出:
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);
}
private static void TestSleepVsTimer(长毫秒差异,整数重复)
{
TimingEventsKeeper TimingEventsKeeper=新的TimingEventsKeeper();
定时VentsKeeper.Reset((int)毫秒差,重复次数);
而(!timingEventsKeeper.TestDoneEvent.IsSet)
{
timingEventsKeeper.CountNextEvent(空);
睡眠((int)毫秒差异);
}
控制台。WriteLine(“睡眠测试:”);
timingEventsKeeper.Output();
定时VentsKeeper.Reset((int)毫秒差,重复次数);
计时器t=新计时器(timingEventsKeeper.CountNextEvent,null,TimeSpan.FromMillicles(1),TimeSpan.FromMillicles(1));
timingEventsKeeper.TestDoneEvent.Wait();
Console.WriteLine(“System.Threading.Timer test:”);
timingEventsKeeper.Output();
}
私人类TimingVentsKeeper
{
长_ticksum=0;
long _casesunt=0;
long_minTicksDiff;
long_maxTicksDiff;
长时间;
内部重复;
public CountdownEvent TestDoneeEvent=新的CountdownEvent(0);
公共无效重置(整数毫秒差异,整数重复)
{
_ticksSum=0;
_casesCount=0;
_minTicksDiff=毫秒差*10000;
_maxTicksDiff=毫秒差*10000;
_lastTicksCount=DateTime.UtcNow.Ticks;
_重复=重复;
TestDoneEvent.Reset(重复);
}
public void CountNextEvent(对象未使用)
{
long currenticksunt=DateTime.UtcNow.Ticks;
long diff=电流互感器-_last互感器;
_LasttickScont=当前tickScont;
TestDoneEvent.Signal();
如果(差异>=\u最大滴答滴答滴答)
{
_maxTicksDiff=diff;
返回;
}
如果(差异0)
WriteLine(“平均时间增量:{0}[ms](来自{1}个案例)”,_ticksSum/_casesunt/10000,_casesunt);
其他的
控制台。WriteLine(“无需计算平均值的测量案例”);
}
}
公共静态类WinApi
{
///TimeBeginPeriod()。有关详细信息,请参阅Windows API文档。
[System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Interoperability”、“CA1401:PinVokes不应可见”)、System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Security”、“CA2118:ReviewSuppressUnmanagedCodeSecurity”)、SuppressUnmanagedCodeSecurity]
[DllImport(“winmm.dll”,EntryPoint=“timeBeginPeriod”,SetLastError=true)]
公共静态外部uint TimeBeginPeriod(uint UMillisSeconds);
///TimeEndPeriod()。有关详细信息,请参阅Windows API文档。
[System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Interoperability”、“CA1401:PinVokes不应可见”)、System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Security”、“CA2118:ReviewSuppressUnmanagedCodeSecurity”)、SuppressUnmanagedCodeSecurity]
[DllImport(“winmm.dll”,EntryPoint=“timeEndPeriod”,SetLastError=true)]
公共静态外部uint TimeEndPeriod(uint uMilliseconds);
}
私有静态void Main(字符串[]args)
{
WinApi.TimeBeginPeriod(1);
TestSleepVsTimer(11000);
WinApi.TimeEndPeriod(1);
}
EDIT1:
环境:
在.NET 2.0、3.0、3.5(无倒计时事件)和4.5下的构建和发布版本上进行测试
在Windows 8(Build 9200)、Server 2012(Build 9200)、Server 2008(Build 6001 SP1)上
在睡眠
和定时器
之间存在显著差异的任何地方
为什么不重复:
正如我所发布的-操作系统计时器分辨率设置为1ms(而且Sleep
不显示该行为)因此,这不是操作系统计时器分辨率(中断频率)的故障。-这是特定于System.Threading.timer的故障
EDIT2:
(添加了TimeBeginPeriod
和TimeEndPeriod
调用代码-强制更改操作系统计时器分辨率)使用从WaitHandle派生的同步类之一,例如AutoResetEvent或ManualResetEvent,在调用WaitOne()方法时设置超时参数
通过在循环中调用WaitOne,可以实现计时器
您可以向wait handle派生类发送信号以中断计时器
注意,要更改分辨率,最好使用实现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;
}
}
尼克
为什么System.Threading.Timer保持15毫秒的分辨率,尽管操作系统时钟分辨率更精确
显然是由于执行。System.Threading.Timer(因此Task.Delay)使用.NET运行时计时器队列,该队列不考虑系统计时器分辨率。此外,我在windows(7,10;server 2012,2016)上运行了测试(.net 4.x),发现WaitHandle.WaitOne()和Monitor.Wait()在WinForms GUI线程上也不遵守系统计时器分辨率(使用WaitHandle)。
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))
{
////...
}