22

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

 while (true)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            sw.Stop();

            if (sw.ElapsedMilliseconds < 0)
                Debugger.Break();

        }

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

4

4 回答 4

17

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

令人鼓舞的解决方法似乎是忽略负值:

long elapsedMilliseconds = Math.Max(0, stopwatch.ElapsedMilliseconds);
于 2009-06-17T17:06:59.457 回答
12

我使用 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 方法中将 elapsed 设置为 0。恕我直言,仍然存在错误:

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

编辑: 不幸的是,#2 和#3 超出了 .NET Framework 的能力:

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

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

不要只使用 .NET 4.0 秒表并假设问题已解决。事实并非如此,除非您希望他们破坏您的线程亲和力,否则他们对此无能为力。从秒表类文档:

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

于 2011-09-22T19:01:25.367 回答
0

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

于 2011-05-22T18:02:13.770 回答
-1

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

Dim tstart AS DateTime = Now
...executing code...
Dim telapsed = (Now - tstart).TotalMilliseconds
于 2011-12-07T16:13:01.347 回答