.Net中的计时器和循环是否准确?

时间:2012-07-17 21:26:58

标签: c# .net loops timer

开发一个程序来计算由555定时器IC产生的脉冲的频率和脉冲宽度,通过PC并行端口进入PC。我注意到每次运行代码时它都显示不同的值,所以我开始测试循环和定时器的准确性。我运行了以下代码,并指出它们不准确(我可能错了,请纠正我,如果我的话!):

对于计时器:

    int sec = 0;
    private void button2_Click(object sender, EventArgs e)
    {
        sec = DateTime.Now.Second;
        i = 0;
        timer1.Enabled = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (sec == DateTime.Now.Second)
        {
            i++;
        }
        else
        {
            timer1.Enabled = false;
            MessageBox.Show(i.ToString(),"Timer Output");
        }
    }

输出: 应该是一样的,但是:

enter image description here enter image description here enter image description here

对于LOOP:

    private void button1_Click(object sender, EventArgs e)
    {
        i = 0;
        CheckForIllegalCrossThreadCalls = false;
        Thread t1 = new Thread(LoopTest);
        t1.Start();
    }

    void LoopTest()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        this.Cursor = Cursors.WaitCursor;
        while (true)
        {
            if (sw.ElapsedMilliseconds != 1000)
            {
                i++;
            }
            else
            {
                break;
            }
        }
        sw.Stop();
        this.Cursor = Cursors.Default;
        MessageBox.Show(i.ToString(), "Loop Output");
    }

输出: 应该是一样的,但是:

enter image description here enter image description here enter image description here

  

我应该怎么做才能使循环和计时器准确,有什么办法吗?或者我必须去艰难而复杂的C代码和DOS?

我认为这是在这个问题中让我错误的价值观的基本原因:Count Parallel port input frequency - C#

4 个答案:

答案 0 :(得分:16)

1)不要使用DateTime.Now进行效果衡量,请使用StopWatch

2)" OUTPUT: Should be same, but .."

他们为什么要这样?您正在非RTOS(实时操作系统)上运行托管/ JIT代码。如果操作系统感觉像这样,您的代码可以随时被弹回。是什么让你相信在这种环境下运行相同的代码N次应该总能产生相同的结果?

3)Windows上的定时器分辨率约为15ms。对于非常准确的计时而言,最好的选择是支持它的系统上的HighPerformanceTimer API(CPU' s)。你还没有向我们展示计时器的间隔时间。

您未能在此考虑许多变量,并且您的预测基于错误的假设。你甚至测量过这段代码多少次?你是否考虑过第一次编译它所需的时间?你在发布模式下运行吗?通过VS?是否有许多任务在后台运行?我可以继续。

答案 1 :(得分:12)

仔细实施允许在大多数Windows平台上测量时间段,精确度仅为几微秒。 请注意以下事实:

  1. Windows不是实时操作系统:这没关系!

  2. 利用流程/主题优先级:SetPriorityClass最多REALTIME_PRIORITY_CLASSSetThreadPriority最多THREAD_PRIORITY_TIME_CRITICAL。确保安全代码,因为这些优先级可以在调用线程忙时锁定系统。 (Process.PriorityClass和Thread.Priority不能将优先级提升到所需的级别。)

  3. 通过Multimedia Timer可能会增加系统中断周期和时间更新间隔。 (包含这些多媒体计时器功能的各种.NET projects。)

  4. 在多核系统上,核心的选择也会影响准确性。强制线程等待定时器事件在Processor0上等待是有利的。 SetThreadAffinityMask函数允许将线程绑定到特定的cpu。 (对于.Net应用程序,请参阅Thread.ProcessorAffinity。)

  5. 使用QueryPerformanceCounterQueryPerformanceFrequency作为高频时间测量的资源。 (对于.Net应用程序,请参阅:Creating a QueryPerfCounter Wrapper Class

  6. 确保校准性能计数器频率的值。 QueryPerformanceFrequency返回的值偏离观测值偏移量和一些热漂移。这可能/将引入许多我们的错误。请参阅Windows Timestamp Project以了解如何进行此类校准。

  7. 并且:是的,您可能需要进行一些硬编码。但是可以观察到微秒的时间 在Windows平台上非常可靠。

    注意:Windows不是实时操作系统。但是Windows上的定时器非常精确。他们完全按照自己应该做的去做,并且准确度很高。关于Windows计时器及其准确性存在很多抱怨的事实是它们的行为很大程度上取决于底层硬件。这也是文档存在许多缺陷的原因。强烈建议您诊断硬件以查找各个时间服务功能。不幸的是,这导致任何程序都有一些额外的代码行与平台无关。

答案 2 :(得分:2)

首先,您不知道调用button2_click时的当前秒数。所以,当显示MessageBox时,它基本上是随机的剩余时间 - 这就是你所看到的。

其次,循环在一段时间内获得的CPU周期数与精度无关。

任何给定线程获得多少个周期取决于系统中还有什么。如果系统决定需要将一大堆周期转移到另一个进程,那么你的线程将会“饥饿”一段时间。

也许你可以详细说明你真正想做的事情,有人可以提供一些建议。

答案 3 :(得分:1)

Ed的回答是正确的:Windows上的计时器不准确。嗯,当然不够准确,无法测量硬件生成的信号。

但是,如果我必须测量频率和脉冲宽度,我会花一百个左右的样本并对它们进行平均。也许是这样:

private StopWatch _Sw = new StopWatch();
private List<TimeSpan> _Samples = new List<TimeSpan>();
private Timer _Timer = new Timer(TimerTick, TimeSpan.FromSeconds(1));
private const int RequiredSamples = 100;

private void StartSampling()
{
    // You can change this next line to PriorityClass.RealTime if you're careful.
    System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                              = PriorityClass.High;
    _Samples.Capacity = RequiredSamples;
    _Timer.Start();
    _Sw.Start();
    Hook555Timer(On555Pulse);
}

private void On555Pulse(object sender, EventArgs e)
{
    _Sample.Add(_Sw.Elapsed);
}

private void TimerTick(object sender, EventArgs e)
{
    if (_Samples.Count > RequiredSamples)
    {
        System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                                    = PriorityClass.Normal;
        _Timer.Stop();
        _Sw.Stop();
        UnHook555Timer(On555Pulse);

        // You can now use the time between each TimeSpan 
        // in _Samples to determine statistics about your timer.
        // Eg: Min / Max duration, average and median duration.
        var durations = _Samples
                .Zip(_Samples.Skip(1), 
                    (a,b) => new { First = a, Second = b } )
                .Select(pair => pair.Second.Subtract(pair.First));
        var minTime = durations.Min(ts => ts.TotalMilliseconds);
        var maxTime = durations.Max(ts => ts.TotalMilliseconds);
        var averageTime = durations.Average(ts => ts.TotalMilliseconds);
        // I don't think LINQ has a Median() aggregate out of the box.
        // Some comment about "an exercise for the reader" goes here.
        var medianTime = durations.Median(ts => ts.TotalMilliseconds);

        var frequency = _Samples.Last()
                                 .Subtract(_Samples.First())
                                      .TotalSeconds / _Samples.Count;
    }
}

(注意:用记事本写的代码,不经过进一步修改就不会起作用)

我的精确度现在由StopWatch而不是Timer确定(请注意,Stopwatch可能不比系统上的Timer更精确,请检查{ {1}}和Frequency属性)。而且我在采样过程中增加了流程的优先级,以最大限度地减少抢占流程的其他流程。

但即便如此,因为Windows不是RTOS,所以你可以做的最好的事情是采用大量的样本并使用一些统计数据来得到答案的近似值。