Search code examples
c#stopwatchatomiclockless

Simple lockless stopwatch


According to MSDN, the Stopwatch class instance methods aren't safe for multithreaded access. This can also be confirmed by inspecting individual methods.

However, since I only need simple "time elapsed" timers at several places in my code, I was wondering if it could still be done lockless, using something like:

public class ElapsedTimer : IElapsedTimer
{
    /// Shared (static) stopwatch instance.
    static readonly Stopwatch _stopwatch = Stopwatch.StartNew();

    /// Stopwatch offset captured at last call to Reset
    long _lastResetTime;

    /// Each instance is immediately reset when created
    public ElapsedTimer()
    { 
        Reset();
    }

    /// Resets this instance.
    public void Reset()
    {
        Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedMilliseconds);
    }

    /// Seconds elapsed since last reset.
    public double SecondsElapsed
    {
        get
        {
             var resetTime = Interlocked.Read(ref _lastResetTime);
             return (_stopwatch.ElapsedMilliseconds - resetTime) / 1000.0;
        }
    }
}

Since _stopwatch.ElapsedMilliseconds is basically a call to QueryPerformanceCounter, I am presuming it's safe to be called from multiple threads? The difference with a regular Stopwatch is that this class is basically running all the time, so I don't need to keep any additonal state ("running" or "stopped"), like the Stopwatch does.

(Update)

After the suggestion made by @Scott in the answer below, I realized that Stopwatch provides a simple static GetTimestamp methods, which returns raw QueryPerformanceCounter ticks. In other words, the code can be modified to this, which is thread safe:

public class ElapsedTimer : IElapsedTimer
{
    static double Frequency = (double)Stopwatch.Frequency;

    /// Stopwatch offset for last reset
    long _lastResetTime;

    public ElapsedTimer()
    { 
        Reset();
    }

    /// Resets this instance.
    public void Reset()
    {
        // must keep in mind that GetTimestamp ticks are NOT DateTime ticks
        // (i.e. they must be divided by Stopwatch.Frequency to get seconds,
        // and Stopwatch.Frequency is hw dependent)
        Interlocked.Exchange(ref _lastResetTime, Stopwatch.GetTimestamp());
    }

    /// Seconds elapsed since last reset
    public double SecondsElapsed
    {
        get
        { 
            var resetTime = Interlocked.Read(ref _lastResetTime);
            return (Stopwatch.GetTimestamp() - resetTime) / Frequency; 
        }
    }
}

The idea of this code, to clarify, is:

  1. to have a simple and fast way of checking if time has elapsed since a certain operation/event,
  2. methods should not corrupt state if called from multiple threads,
  3. must be insensitive to OS clock changes (user changes, NTP sync, time zone, etc.)

I would use it similar to this:

private readonly ElapsedTimer _lastCommandReceiveTime = new ElapsedTimer();

// can be invoked by multiple threads (usually threadpool)
void Port_CommandReceived(Cmd command)
{
    _lastCommandReceiveTime.Reset();
}

// also can be run from multiple threads
void DoStuff()
{
    if (_lastCommandReceiveTime.SecondsElapsed > 10)
    {
        // must do something
    }
}

Solution

  • The only change I would suggest is use Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks); instead of Milliseconds because if you are in high performance mode it is possible to get sub millisecond results from QueryPerformanceCounter.