C#为什么定时器频率极度偏离?

时间:2009-01-06 13:33:16

标签: c# timer frequency deviation

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 Hz;实际:4,8 Hz
要求:10赫兹;实际:9,1 Hz
要求:15赫兹;实际:12,7 Hz
要求:20赫兹;实际:16 Hz
要求:30赫兹;实际:21,3 Hz
要求:50赫兹;实际:31,8 Hz
要求:75赫兹;实际:63,9 Hz
要求:100赫兹;实际:63,8 Hz
要求:200赫兹;实际:63,9 Hz
要求:500赫兹;实际:63,9 Hz

实际频率偏离所要求的频率高达36%。 (显然不能超过64赫兹。)鉴于微软建议这个计时器比System.Windows.Forms.Timer更“准确”,这让我很困惑。

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

在我的实际程序中,我需要以每秒50个样本的速度收集测量结果。这不应该需要实时系统。每秒获得32个样本而不是50个非常令人沮丧。

有什么想法吗?

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

10 个答案:

答案 0 :(得分:23)

如果使用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

希望这有帮助。

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:4)

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

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

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

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

答案 3 :(得分:3)

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

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

答案 4 :(得分:3)

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

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

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

否则你可能会看一下multimedia timers(PInvoke)。

答案 5 :(得分:2)

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

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

答案 6 :(得分:2)

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

答案 7 :(得分:1)

定时器关闭的原因有很多。硬件示例,相同的线程忙于另一个处理,等等......

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

答案 8 :(得分:0)

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

  1. 您要求等待的时间
  2. #1发生且进程在调度队列中转入之间的时间。
  3. 计时器可以很好地控制#1,但几乎无法控制#2。它可以向操作系统发出信号,表示它想再次运行,但操作系统可以随时将其唤醒。

答案 9 :(得分:0)

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