2016-06-13 25 views
2

MSDNによると、Stopwatchクラスのインスタンスメソッドは、マルチスレッドアクセスでは安全ではありません。これは、個々の方法を調べることによっても確認することができます。 _stopwatch.ElapsedMilliseconds以来単純なロックレスストップウォッチ

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

は基本的である:私は必要があるので

しかし、単純な、それはまだのようなものを使用して、ロックなしに行うことができれば、私のコード内のいくつかの場所でのタイマーは、私が思っていた「時間が経過しました」 QueryPerformanceCounterを呼び出すと、複数のスレッドから呼び出すのが安全だと思われますか?通常のStopwatchとの違いは、このクラスは基本的に常に実行されているため、Stopwatchのようにadditonal状態( "running"または "stopped")を保持する必要はありません。

(更新)は、以下の回答で@Scott製提案した後、私はStopwatchQueryPerformanceCounterティックを返す単純な静的GetTimestamp方法を提供することに気づきました。言い換えれば、コードはスレッドセーフである、このように変更することができます。

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. から呼び出された場合破損状態(等ユーザーの変更、NTP同期、時間帯、)OSクロック変化に鈍感でなければならないはず

私はこれまで、それは似て使用します。

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命令にJITされることさえあります。 – Lou

+1

これまでのところ、QueryPerformanceCounterを自分で呼び出すのはなぜですか? –

答えて

1

高性能モードの場合は、サブミリ秒の結果をQueryPerformanceCounterから得ることができるため、Millisecondsの代わりにInterlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks);を使用することをお勧めします。

+0

+1ありがとう!しかし、私は 'Stopwatch'ダニは実際に' 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作業が作業を行うことを前提としてい別のスレッドでは、完了したときにコールバックを呼び出し、呼び出し元のスレッドにマーシャリングします。

2番目の例では、呼び出し元がスレッドを処理していることを前提としています。この関数は、タイミング自体をすべて実行し、結果を呼び出し元に返します。

+0

しかし、全体のポイントは、どのスレッドからもウォッチドッグをリセットし、時間が経過したかどうかを安全に確認できることです。また、最初のスニペットは 'watch'変数を取得しますが、非同期メソッドの外部にアクセスするのを防ぐものは何もしません。私は基本的に静的なp/invokeを 'QueryPerformanceCounter'に必要とするだけなので' static ''Stopwatch'を使用していますので、タイマーの各インスタンスごとにそのフィールドをすべて割り当てる必要はありません(' long'が1つだけ必要です) 。 – Lou

+0

あなたはロックをしなければならないでしょう。 –