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(与重复的问题相反)。因此这不是系统计时器分辨率的问题。因此,假设重复的问题中没有有用的信息

背景: 似乎.NET
System.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))
{
    ////...
}