问题:
为什么在操作系统时钟分辨率更精确的情况下,System.Threading.Timer仍保持15毫秒的分辨率?
在没有繁忙CPU等待的情况下,建议采用什么方法来实现1MS计时事件解析?
再次强调:在我的例子中,系统计时器有1毫秒的分辨率(与重复的问题相反)。所以这不是系统计时器分辨率的问题。因此,在所谓的重复问题中没有有用的信息。
背景:
似乎.netSystem.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(不带倒计时事件)和4.5下的生成和发布版本上测试
在Windows 8(内部版本9200)、Server 2012(内部版本9200)、Server 2008(内部版本6001 SP1)上
SleepTimer之间差异显著。
为什么这不是重复的:
正如我所发布的-操作系统计时器分辨率设置为1毫秒(而且Sleep也不会显示这种行为)。因此,这不是操作系统计时器分辨率(中断频率)的错误-这是System.Threading.Timer特有的。
编辑2:
(添加了TimeBeginPeriodTimeEndPeriod对代码的调用-以强制更改操作系统计时器分辨率)

最佳答案

为什么System.Threading.Timer在操作系统时钟分辨率更高的情况下仍保持15毫秒的分辨率?
显然是因为实施。system.threading.timer(因此task.delay)使用.NET运行时计时器队列,不考虑系统计时器的分辨率。此外,我运行了测试(.net 4.x)windows(7,10;server 2012,2016),发现waithandle.waitone()和monitor.wait()也不尊重winforms gui线程上的系统计时器解析(使用waithandle需要answer above)。所以,只有线程。睡眠尊重它在gui线程。
在没有繁忙CPU等待的情况下,建议采用什么方法来实现1MS计时事件解析?
Jim Mischel指出的一种方法。但是,它有如下缺点:
在Windows线程池线程上执行回调。
时间间隔是相对于当前时间的。
时间间隔为整数毫秒,因此理论上最大精度为1毫秒。
通过许多报告,1.5-2ms的精度实际上是可以达到的最大值,并且只有TimeNeXT周期(1)调用。
另一种方法是:NtSetTimerResolutionWaitable Timer Objects
您可以获得0.5毫秒的分辨率(取决于硬件和Windows版本)。
对于c example(它不是计时器类的示例,而是在c中使用此函数的示例),您可以检查这个article
您也可以尝试Nick's建议,但需要记住gui线程的问题。

07-24 09:48
查看更多