System.Diagnostics.Stopwatch在Elapsed …属性中返回负数

秒表可以返回负值是否正常? 下面的代码示例可用于重现它。

while (true) { Stopwatch sw = new Stopwatch(); sw.Start(); sw.Stop(); if (sw.ElapsedMilliseconds < 0) Debugger.Break(); } 

我可以重现负数的唯一地方是我的虚拟机(由8核机器上的Hyper-V托管)

这是一个错误 。 它似乎并没有引起太多关注,所以我建议跟进那份报告。

不起眼的解决方法似乎是忽略负值:

 long elapsedMilliseconds = Math.Max(0, stopwatch.ElapsedMilliseconds); 

我使用Reflector在.NET 2.0和.NET 4.0中反编译了Stopwatch类,然后比较差异以了解它是如何修复的。 除了添加新的Restart方法之外,这是我发现的差异:

 public void Stop() { if (this.isRunning) { long num2 = GetTimestamp() - this.startTimeStamp; this.elapsed += num2; this.isRunning = false; // THE NEXT 4 LINES ARE NEW IN .NET 4.0: if (this.elapsed < 0L) { this.elapsed = 0L; } } } 

所以基本上,如果值为负,它们会在Stop方法中设置为0。 还有bug恕我直言:

  1. 如果用户在停止秒表之前读取任何Elapsed属性怎么办? 您可能仍会得到负值。
  2. 重置为0不正确。 一段时间不得不过去,即使它只有几微秒!
  3. 它没有处理我和其他人报告的exception大的正经过值。

编辑:不幸的是,#2和#3超出了.NET Framework的强大function:

以下是问题的核心:来自QueryPerformanceCounter上的MSDN,它是Stopwatch类使用的API:

在多处理器计算机上,调用哪个处理器无关紧要。 但是,由于基本输入/输出系统(BIOS)或硬件抽象层(HAL)中的错误,您可以在不同的处理器上获得不同的结果。 要指定线程的处理器关联,请使用SetThreadAffinityMask函数。

不要只使用.NET 4.0秒表并假设问题得到解决。 它不是,除非你希望它们与你的线程亲和力混淆,否则他们无法做任何事情。 从Stopwatch类文档:

在多处理器计算机上,线程运行在哪个处理器上并不重要。 但是,由于BIOS或硬件抽象层(HAL)中的错误,您可以在不同的处理器上获得不同的时序结果。 要指定线程的处理器关联,请使用ProcessThread.ProcessorAffinity方法。

Microsoft Connect Bug上的“Closed as Fixed”解决方案必须意味着他们已经在.NET 4中修复了它。我在桌面和VM上运行了repro代码几分钟并且没有负值。

我发现在VM中获得正确的运行时间的唯一解决方法是(VB):

 Dim tstart AS DateTime = Now ...executing code... Dim telapsed = (Now - tstart).TotalMilliseconds