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

System.Timers.TimerSystem.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); } 

输出如下:

要求: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个样本非常令人沮丧。

有任何想法吗?

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

好吧,我实际上得到的数字高达100赫兹,有一些很大的偏差,但在大多数情况下接近所要求的数字(使用最新的.NET SP运行XP SP3)。

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

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

否则你可能会看一下多媒体计时器 (PInvoke)。

如果你使用winmm.dll,你可以使用更多的CPU时间,但有更好的控制。

以下是修改为使用winmm.dll计时器的示例

 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 

希望这可以帮助。

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

http://www.arm.com/markets/emerging_applications/armpp/8070.html

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

看起来您的实际定时器频率为63.9 Hz或其整数倍。

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

这是基于’tick’的整数倍的定时器(在DOS中,例如’tick’值是55毫秒/ 18Hz)。

我不知道为什么你的滴答计数是15.65 mec而不是15 msec。 作为一个实验,如果你在计时器处理程序中睡了几毫秒怎么办?我们可能会看到刻度线之间的15毫秒,以及每个刻度线的计时器处理程序中的0.65毫秒?

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

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

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

http://www.pharlap.com/rtx.htm

这是一个很好的使用多媒体计时器的计时器实现http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

计时器关闭的原因有很多。 硬件示例,同一个线程忙于另一个处理,依此类推……

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

部分问题是计时器有两个延迟 。 这取决于在OS中如何实现定时器。

  1. 您请求等待的时间
  2. #1发生且进程在调度队列中转到的时间。

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

根据您的评论,您根本不应该使用计时器。 您应该使用带秒表的循环来检查间隔和自旋锁,这样您就不会丢失量子。