C# 为什么定时器频率非常低?

C# 为什么定时器频率非常低?,c#,timer,frequency,deviation,C#,Timer,Frequency,Deviation,System.Timers.Timer和System.Threading.Timer都会以与请求的时间间隔不同的时间间隔启动。 例如: new System.Timers.Timer(1000d / 20); 产生每秒触发16次而不是20次的计时器 为了确保过长的事件处理程序不会产生副作用,我编写了以下小测试程序: int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 }; // Test System.Timers.T

System.Timers.Timer
System.Threading.Timer
都会以与请求的时间间隔不同的时间间隔启动。 例如:

new System.Timers.Timer(1000d / 20);
产生每秒触发16次而不是20次的计时器

为了确保过长的事件处理程序不会产生副作用,我编写了以下小测试程序:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}
int[]频率={5,10,15,20,30,50,75,100,200,500};
//测试系统。计时器。计时器
foreach(整数频率,以频率表示)
{
整数计数=0;
//初始化定时器
System.Timers.Timer Timer=新的System.Timers.Timer(1000d/频率);
timer.appead+=委托{Interlocked.Increment(ref count);};
//数到10秒
DateTime start=DateTime.Now;
timer.Enabled=true;
while(DateTime.Now
输出如下所示:

要求:5赫兹;实际频率:4,8赫兹
要求:10赫兹;实际频率:9,1赫兹
要求:15赫兹;实际频率:12,7赫兹
要求:20赫兹;实际频率:16赫兹
要求:30赫兹;实际频率:21,3赫兹
要求:50赫兹;实际频率:31,8赫兹
要求:75赫兹;实际频率:63,9赫兹
要求:100赫兹;实际频率:63,8赫兹
要求:200赫兹;实际频率:63,9赫兹
要求:500赫兹;实际频率:63,9赫兹

实际频率与请求频率的偏差高达36%。(而且显然不能超过64赫兹。)鉴于微软推荐这种定时器是因为它比
System.Windows.Forms.timer
更“精确”,这让我感到困惑

顺便说一句,这些不是随机偏差。它们每次都是相同的值。 对于另一个计时器类,
System.Threading.timer
,一个类似的测试程序显示了完全相同的结果

在我的实际程序中,我需要每秒精确采集50个样本。这还不需要一个实时系统。而且每秒获得32个样本而不是50个样本是非常令人沮丧的

有什么想法吗

@克里斯:
你是对的,间隔似乎都是1/64秒的整数倍。顺便说一句,在事件处理程序中添加Thread.Sleep(…)没有任何区别。考虑到
System.Threading.Timer
使用线程池,因此每个事件都在空闲线程上触发,这是有道理的。

定时器频率关闭的原因很多。例如,在硬件中,同一线程正忙于另一个处理,等等


如果需要更准确的时间,请使用System.Diagnostics命名空间中的Stopwatch类。

这些类不用于实时使用,并且受Windows等操作系统的动态调度特性的约束。如果您需要实时执行,您可能需要查看一些嵌入式硬件。我不是100%确定,但我认为.netcpu可能是芯片上较小的.NET运行时的实时版本


当然,您需要评估这些时间间隔的准确性对于附加到它们的代码将在非实时操作系统上执行的重要性。当然,除非这是一个纯粹的学术问题(在这种情况下-是的,这很有趣!:P)。

看起来您的实际计时器频率是63.9 Hz或其整数倍

这意味着计时器分辨率约为15毫秒(或其整数倍,即30毫秒、45毫秒等)

这是基于“滴答声”整数倍的定时器的预期值(例如在DOS中,“滴答声”值为55毫秒/18赫兹)

我不知道为什么你的滴答数是15.65兆欧而不是15毫秒。作为一个实验,如果您在计时器处理程序中睡眠几毫秒,会怎么样:我们可能会看到计时间隔为15毫秒,而计时器处理程序中的每个计时间隔为0.65毫秒吗?

Windows(因此,在其上运行的.NET)是一个抢占式多任务操作系统。任何给定的线程都可以在任何时候被另一个线程停止,如果抢占线程的行为不正常,您将无法在需要它或需要它时获得控制权


简而言之,这就是为什么不能保证您获得准确的计时,以及为什么Windows和.NET不适合某些类型的软件。如果生命处于危险之中,因为您无法在需要的时候获得控制权,请选择不同的平台。

事实上,我得到的是不同的数字,高达100 Hz,有一些较大的偏差,但在大多数情况下更接近请求的数字(使用最新的.NET SP运行XP SP3)

System.Timer.Timer是使用System.Threading.Timer实现的,因此这解释了为什么会看到相同的结果。我假设计时器是使用某种调度算法等实现的(它的内部调用,也许看看Rotor 2.0会对它有所帮助)

我建议使用另一个线程(或其组合)调用Sleep和回调来实现一种计时器。不过,我不确定结果如何


否则你可能会看一看(平沃克)

部分问题是计时器有两个延迟。这取决于计时器在操作系统中的实现方式

  • 您请求等待的时间
  • 从#1发生到进程在调度队列中轮到它之间的时间

  • 计时器可以很好地控制#1,但几乎无法控制#2。它可以向操作系统发出想要再次运行的信号,但操作系统可以在感觉需要时将其唤醒。

    如果您确实需要跳转到实时环境,我过去在需要确定性采样(从自定义串行设备)时使用过RTX,并且运气很好


    如果您使用winmm.dll,您可以使用更多的CPU时间,但具有更好的控制能力

    下面是修改后使用
    const String WINMM = "winmm.dll";
    const String KERNEL32 = "kernel32.dll";
    
    delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);
    
    [DllImport(WINMM)]
    static extern uint timeSetEvent(
          UInt32            uDelay,      
          UInt32            uResolution, 
          [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
          UInt32            dwUser,      
          Int32             fuEvent      
        );
    
    [DllImport(WINMM)]
    static extern uint timeKillEvent(uint uTimerID);
    
    // Library used for more accurate timing
    [DllImport(KERNEL32)]
    static extern bool QueryPerformanceCounter(out long PerformanceCount);
    [DllImport(KERNEL32)]
    static extern bool QueryPerformanceFrequency(out long Frequency);
    
    static long CPUFrequency;
    
    static int count;
    
    static void Main(string[] args)
    {            
        QueryPerformanceFrequency(out CPUFrequency);
    
        int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };
    
        foreach (int freq in frequencies)
        {
            count = 0;
    
            long start = GetTimestamp();
    
            // start timer
            uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);
    
            // wait 10 seconds
            while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
            {
                Thread.Sleep(1);
            }
    
            // end timer
            timeKillEvent(timerId);
    
            Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
        }
    
        Console.ReadLine();
    }
    
    static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
    {
        Interlocked.Increment(ref count);
    }
    
    static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
    {
        return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
    }
    
    static public long GetTimestamp()
    {
        long result;
        QueryPerformanceCounter(out result);
        return result;
    }
    
    Requested frequency: 5
    Actual frequency: 5
    
    Requested frequency: 10
    Actual frequency: 10
    
    Requested frequency: 15
    Actual frequency: 15
    
    Requested frequency: 20
    Actual frequency: 19
    
    Requested frequency: 30
    Actual frequency: 30
    
    Requested frequency: 50
    Actual frequency: 50
    
    Requested frequency: 75
    Actual frequency: 76
    
    Requested frequency: 100
    Actual frequency: 100
    
    Requested frequency: 200
    Actual frequency: 200
    
    Requested frequency: 500
    Actual frequency: 500