2016-06-13 66 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

你將不得不鎖定。 –