2016-06-13 51 views
2

根据MSDN,Stopwatch类实例方法对于多线程访问不安全。这也可以通过检查个别方法加以确认。简单的无锁秒表

然而,因为我只需要简单的“时间流逝”在我的代码的几个地方定时器,我想知道,如果它仍然可以使用像做无锁,:

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

由于_stopwatch.ElapsedMilliseconds基本上是一个致电QueryPerformanceCounter,我假设从多线程调用是安全的?与常规Stopwatch的区别在于,此类基本上一直在运行,所以我不需要保留任何其他状态(“运行”或“停止”),就像Stopwatch一样。

(更新)

通过@Scott在下面的答案提出的建议之后,我意识到Stopwatch提供一个简单的静态GetTimestamp方法,该方法返回原QueryPerformanceCounter蜱。换句话说,代码可以被修改,以这一点,这是线程安全的:

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

这段代码的思想,澄清是:

  1. 有检查的一种简单,快捷的方式如果由于某种操作/事件时间已经过去时,
  2. 方法应该如果从多个线程调用时,未损坏状态
  3. 必须是不敏感的OS时钟变化(用户改变,NTP同步,时区等)

我会用它与此类似:

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 
    } 
} 
+0

'Interlocked.Exchange'和'Interlocked.Read'是一个锁定机制,我相信 –

+0

@ justin.m.chase:不,它们都是无锁的(同时确保原子性)。在x64平台上,他们甚至会根据实际的CPU指令进行打印。 – Lou

+1

如果你要这么做,为什么不自己调用QueryPerformanceCounter? –

回答

1

我建议的唯一更改是使用Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks);而不是毫秒,因为如果您处于高性能模式,可以从QueryPerformanceCounter获得亚毫秒结果。

+0

+1谢谢!一个更正虽然:我相信'秒表'滴答声实际上是'QueryPerformanceCounter'返回的原始滴答声,而不是'DateTime'使用的100ns滴答声。 – Lou

+0

你说得对,我会解决我的答案。 –

0

我会建议创建Stopwatch的多个实例,并从中读取仅在同一线程上。

我不知道你的异步代码是什么样子,但在伪代码,我会做两种:

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

或者:

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

第一个例子假定DoAsyncWork工作做的工作在一个不同的线程中,然后在完成时调用回调函数,然后再回到调用者线程。

第二个例子假定调用者正在处理线程,并且该函数完成所有计时本身,并将结果传递给调用者。

+0

但是,重点是能够从任何线程重置看门狗,并安全地检查时间是否已经过去。此外,您的第一个代码片段捕获“watch”变量,但不会阻止它在异步方法之外访问。我使用'static''Stopwatch',因为我基本上只需要静态p/invoke到'QueryPerformanceCounter',所以不需要为每个定时器的每个实例分配它的所有字段(只需要一个'long') 。 – Lou

+0

你将不得不锁定。 –