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:
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
}
}
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
.