The StopWatch class isn't inherently thread-safe. Are you
synchronizing access to it appropriately. Can you post some code
demonstrating the problem?
Right. The StopWatch class expects that the Reset/Start/Stop/Elapsed*
methods are all accessed from the same thread. Not only that, they all
have to be accessed from the same CPU. Does StopWatch use the
BeginThreadAffinity internally to make this happen? I'll assume that
it does. The problem is that if you need to call Reset in one thread
and Elapsed in another, this is very difficult. I did manage to make a
class to do it, however. Here it is for your critique:
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
namespace Blah
{
/// <summary>
/// Represents a high-resolution stopwatch that is thread-safe and
CPU safe.
/// </summary>
public sealed class StopWatch
{
static private Thread _backgroundThread = null;
static private EventWaitHandle _tickCountUpdateRequest = new
EventWaitHandle(false, EventResetMode.AutoReset);
static private EventWaitHandle _tickCountUpdateDone = new
EventWaitHandle(false, EventResetMode.AutoReset);
static public long Frequency = 0;
static public long CurrentTickCount = 0;
public long StartTickCount = 0;
/// <summary>
/// A thread to make sure CPU tick counts are always read from the
same CPU
/// </summary>
private static void _backgroundThreadFunc()
{
Thread.BeginThreadAffinity();
Frequency = System.Diagnostics.Stopwatch.Frequency;
while (true)
{
_tickCountUpdateRequest.WaitOne();
CurrentTickCount = System.Diagnostics.Stopwatch.GetTimestamp();
_tickCountUpdateDone.Set();
}
//Thread.EndThreadAffinity(); // never called
}
/// <summary>
/// The System.Diagnostics.Stopwatch (and hence, performance
counters) get their tick marks from the CPU that the thread is on. We
create a thread to make sure the tick counts are always read on the
same CPU.
/// </summary>
static StopWatch() {
_backgroundThread = new Thread(new
ThreadStart(_backgroundThreadFunc));
_backgroundThread.IsBackground = true;
_backgroundThread.Start();
}
/// <summary>
/// Initializes a new instance of the StopWatch class.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public StopWatch()
{
Reset();
}
/// <summary>
/// Resets the stopwatch. This method should be called when you
start measuring.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public void Reset()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
StartTickCount = CurrentTickCount;
}
}
/// <summary>
/// Peg the processor for this many seconds; mainly used for testing
/// </summary>
/// <param name="seconds"></param>
public static void BusyLoop(double seconds)
{
DateTime endTime = DateTime.Now.AddSeconds(seconds);
int i = 0;
while (DateTime.Now < endTime) { i++; }
}
public long GetCurrentTime_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return CurrentTickCount * 1000 / StopWatch.Frequency;
}
}
/// <summary>
/// Get the time elapsed, in seconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in seconds.</returns>
public double GetElapsed_s()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (double)(CurrentTickCount - StartTickCount) /
(double)StopWatch.Frequency;
}
}
/// <summary>
/// Get the time elapsed, in milliseconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in milliseconds.</returns>
public long GetElapsed_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (CurrentTickCount - StartTickCount) * 1000 /
StopWatch.Frequency;
}
}
}
}