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

在开发一个程序来计算由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"); } } 

输出:应该相同,但是:

在此处输入图像描述在此处输入图像描述在此处输入图像描述

对于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"); } 

输出:应该相同,但是:

在此处输入图像描述在此处输入图像描述在此处输入图像描述

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

我认为这是在这个问题中给我错误的值的基本原因: 计数并行端口输入频率 – C#

1)不要使用DateTime.Now进行性能测量,请使用StopWatch

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

为什么他们呢? 您正在非RTOS(实时操作系统)上运行托管/ JIT代码。 如果操作系统感觉像你那么代码可以在任何时候被反弹。 是什么让你相信在这种环境中运行相同的代码N次应该总是产生相同的结果到如此小的程度?

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

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

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

  1. Windows不是实时操作系统:这在这里无关紧要!

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

  3. 通过多媒体计时器可能会增加系统中断周期和时间更新间隔。 (有各种.NET项目可以包含这些多媒体计时器function。)

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

  5. 使用QueryPerformanceCounter和QueryPerformanceFrequency作为高频时间测量的资源。 (对于.Net应用程序,请参阅: 创建QueryPerfCounter包装类 )

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

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

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

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

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

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

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

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

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

 private StopWatch _Sw = new StopWatch(); private List _Samples = new List(); 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精确,检查FrequencyIsHighResolution属性)。 而且我在采样过程中增加了流程的优先级,以最大限度地减少抢占流程的其他流程。

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