22

Is it normal behaviour that Stopwatch can return negative values? Code sample below can be used to reproduce it.

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

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

        }

The only place where I can reproduce negative numbers is my virtual machine (hosted by Hyper-V on a 8-core machine)

Vadym Stetsiak
  • 1,974
  • 18
  • 22

4 Answers4

17

This is a bug. It doesn't seem to have a lot of attention around it, through, so I'd suggesting following up with that report.

The uninspiring workaround appears to be to ignore negative values:

long elapsedMilliseconds = Math.Max(0, stopwatch.ElapsedMilliseconds);
Michael Haren
  • 105,752
  • 40
  • 168
  • 205
  • It seems that the only way to reproduce it is to use a virtual machine. I've tried the code on several desktop machines - no success – Vadym Stetsiak Jun 17 '09 at 17:09
  • 5
    This work-around only eliminates negative values - it does not eliminate the extremely high (false) positive values it also returns. –  Jul 02 '09 at 03:11
  • It's working in .NET 4, but is difficult to believe that the bug still exists in .NET 3.5 :( – vtortola May 03 '11 at 10:02
  • It's not completely fixed in .NET 4; see my comments in the Connect report and in my answer... – James Johnston Sep 22 '11 at 19:02
12

I decompiled the Stopwatch class in both .NET 2.0 and .NET 4.0 using Reflector and then compared the differences to see how it was fixed. Besides the addition of the new Restart method, this is all I found for a difference:

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;
        }
    }
}

So basically they set elapsed to 0 in the Stop method if the value is negative. There are still bugs IMHO:

  1. What if the user reads any of the Elapsed properties before stopping the stopwatch? You might still get a negative value.
  2. Resetting to 0 is not correct. Some time had to have elapsed, even if it was only a few microseconds!
  3. It does nothing to handle the unusually large positive elapsed values that I and others have reported.

EDIT: Unfortunately, #2 and #3 are beyond the power of the .NET Framework:

Here's the core of the problem: from MSDN on QueryPerformanceCounter which is the API used by the Stopwatch class:

On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL). To specify processor affinity for a thread, use the SetThreadAffinityMask function.

Don't just use .NET 4.0 stopwatch and assume the problem is fixed. It isn't and they can't do anything about it unless you want them to muck with your thread affinity. From the Stopwatch class documentation:

On a multiprocessor computer, it does not matter which processor the thread runs on. However, because of bugs in the BIOS or the Hardware Abstraction Layer (HAL), you can get different timing results on different processors. To specify processor affinity for a thread, use the ProcessThread.ProcessorAffinity method.

James Johnston
  • 9,264
  • 9
  • 48
  • 76
0

The "Closed as Fixed" resolution on the Microsoft Connect Bug must mean that they fixed it in .NET 4. I've run the repro code on my desktop and a VM for several minutes and got no negative values.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113
-1

The only workaround that I've found to get the correct elapsed time in a VM is (VB):

Dim tstart AS DateTime = Now
...executing code...
Dim telapsed = (Now - tstart).TotalMilliseconds
LostAtC
  • 17
  • 1
  • downvote wasn't me ... but maybe because the `Ticks` property of `DateTime.Now` has not the same resolution as a `Stopwatch` (as far as I recall, the `Ticks` of `DateTime.Now` only get updated each 15ms) –  Nov 08 '12 at 11:42
  • 1
    Note that the `Now` property will jump ahead or forward dramatically during daylight savings time transitions. It's also much, much slower than `UtcNow`. – James Nov 22 '14 at 02:52
  • 1
    This is compeletely wrong, never use system time to measure code execution time, system time can change due to time synchronization and can be easily changed by user. – Simon Nov 04 '15 at 07:51