5

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
    }
}
Lou
  • 4,244
  • 3
  • 33
  • 72
  • `Interlocked.Exchange` and `Interlocked.Read` are a locking mechanisms I believe – justin.m.chase Jun 13 '16 at 21:49
  • @justin.m.chase: no, they are both lockless (while ensuring atomicity). On x64 platforms they even get JITted to actual CPU instructions. – Lou Jun 13 '16 at 21:51
  • 1
    If you are going that far, why not just call QueryPerformanceCounter yourself? – Scott Chamberlain Jun 13 '16 at 21:51
  • 1
    @ScottChamberlain: of course, I can do that too, I just wanted to check if there is something I am missing in this approach? That would be better in any case, since `Stopwatch` implementation doesn't have to stay fixed in future .NET versions. The only benefit is that it converts the ticks to milliseconds internally, so I don't have to check `TickFrequency`. – Lou Jun 13 '16 at 21:52
  • The code/approach you suggest seems reasonable enough, though I have sympathy with @ScottChamberlain - it might be better to implement the class you actually need than (re|ab)use Stopwatch. Of course there are upsides and downsides so I think only you can say. – rivimey Jun 13 '16 at 22:00
  • They are atomic but I don't know why you say they're lockless. That is all that they are essentially, is a simple way to do atomic locking operations. – justin.m.chase Jun 13 '16 at 22:01
  • From MSDN (https://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx) "The following code example shows a thread-safe resource locking mechanism." – justin.m.chase Jun 13 '16 at 22:02
  • You might want to post why you want to do this. There may be a better design. See XY problem. – paparazzo Jun 13 '16 at 22:09
  • @justin.m.chase: I am saying they are lockless because they don't cause context switches like `Monitor`, or the `lock` keyword. They perform the atomic operation on the CPU level, if possible using intrinsics. You can read the [Albahari article](http://www.albahari.com/threading/part4.aspx#_Interlocked): *Interlocked’s methods have a typical overhead of 10 ns — half that of an uncontended lock. Further, they can never suffer the additional cost of context switching due to blocking.* Compare that to ~1us for a full `lock` context switch. – Lou Jun 13 '16 at 22:13
  • 1
    @justin.m.chase: in other words, it's 2x faster when there is no contention, and 100x faster when two threads are competing. For multiple threads, performance differences would be even greater. – Lou Jun 13 '16 at 22:15
  • @justin, there's a difference between atomicity and locking. – wablab Jun 13 '16 at 22:18
  • The **(Update)** section of the answer looks to me more like an attempt to answer the question, than to clarify it. If your intention was to answer the question, I would suggest to remove this section from the question and post it as a [self-answer](https://stackoverflow.com/help/self-answer) instead. – Theodor Zoulias Aug 13 '22 at 06:11

2 Answers2

2

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.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • +1 Thanks! One correction though: I believe `Stopwatch` ticks are actually raw ticks returned by `QueryPerformanceCounter`, not 100ns ticks used by `DateTime`. – Lou Jun 13 '16 at 22:34
0

I would recommend creating multiple instances of the Stopwatch and only reading from it on the same thread.

I don't know what your asynchronous code looks like but in psuedo code I would do either:

Stopwatch watch = Stopwatch.Startnew();
DoAsyncWork((err, result) =>
{
  Console.WriteLine("Time Elapsed:" + (watch.ElapsedMilliseconds / 1000.0));
  // process results...
});

Or:

public DoAsyncWork(callback) // called asynchronously
{
  Stopwatch watch = Stopwatch.Startnew();
  // do work
  var time = watch.ElapsedMilliseconds / 1000.0;
  callback(null, new { time: time });
}

The first example assumes that DoAsyncWork work does the work in a different thread then calls the callback when completed, marshalling back to the callers thread.

The second example assumes the caller is handling the threading and this function does all of the timing itself, passing the result back to the caller.

justin.m.chase
  • 13,061
  • 8
  • 52
  • 100
  • But the whole point is to be able to reset the watchdog from any thread, and safely check if the time has elapsed. Also, your first snippet captures the `watch` variable, but does nothing to prevent it from being accessed outside the async method. I am using a `static` `Stopwatch` because I basically only need the static p/invoke to `QueryPerformanceCounter`, so there is no need to allocate all its fields per each instance of my timer (which only needs one `long`). – Lou Jun 13 '16 at 22:02
  • You're going to have to do locking then. – justin.m.chase Jun 13 '16 at 22:04