2017-01-17 4 views
2

私は25msごとに起動するタイマーが必要です。私はドットネットコアランタイムと最新のモノランタイムの両方で、Windows 10とLinux(Ubuntu Server 16.10と12.04)の間のデフォルトTimerの実装を比較してきました。C#タイマー解像度:Linux(モノ、ドットネットコア)vs Windows

タイマー精度には多少の違いがあります。

私はタイマーをテストするために次のコードを使用しています:Windows上

// inside Main() 
     var s = new Stopwatch(); 
     var offsets = new List<long>(); 

     const int interval = 25; 
     using (var t = new Timer((obj) => 
     { 
      offsets.Add(s.ElapsedMilliseconds); 
      s.Restart(); 
     }, null, 0, interval)) 
     { 
      s.Start(); 
      Thread.Sleep(5000); 
     } 

     foreach(var n in offsets) 
     { 
      Console.WriteLine(n); 
     } 

     Console.WriteLine(offsets.Average(n => Math.Abs(interval - n))); 

それはあらゆる場所にあります:

... 
36 
25 
36 
26 
36 
5,8875 # <-- average timing error 

Linux上DOTNETコアを使用して、それはすべての上に少ないのです場所:

... 
25 
30 
27 
28 
27 
2.59776536312849 # <-- average timing error 

しかし、モノTimerは非常に正確である:

... 
25 
25 
24 
25 
25 
25 
0.33 # <-- average timing error 

編集:でも、窓に、モノはまだそのタイミング精度を維持します。

... 
25 
25 
25 
25 
25 
25 
25 
24 
0.31 

この違いを引き起こしていますか?ドットネットのコアランタイムが、モノと比べて物事を行う方法に利点がありますか?失われた精度を正当化するものですか?

+0

2017実際の解決策はまだありません。なぜ誰かが各プラットフォームでネイティブのC++マルチメディアタイマーをまとめないのですか?何か問題があるかどうかわからない? –

+0

@ b.ben Windowsでは、Timerクラスのモノ実装をCLR(モノラルを使用せず)で使用することが実際に可能であると信じています。同じ精度が得られます。ここでの問題は単なるSTL実装です。 – Size43

答えて

2

残念ながら、.NETフレームワークのタイマーに依存することはできません。最高のものは、ミリ秒ごとにトリガしたい場合でも15msの周波数を持っています。しかし、マイクロ秒精度の高解像度タイマーを実装することもできます。

注:これは、Stopwatch.IsHighResolutionがtrueを返す場合にのみ機能します。 Windowsでは、これはWindows XPから始まります。しかし、私は他のフレームワークをテストしませんでした。

public class HiResTimer 
{ 
    // The number of ticks per one millisecond. 
    private static readonly float tickFrequency = 1000f/Stopwatch.Frequency; 

    public event EventHandler<HiResTimerElapsedEventArgs> Elapsed; 

    private volatile float interval; 
    private volatile bool isRunning; 

    public HiResTimer() : this(1f) 
    { 
    } 

    public HiResTimer(float interval) 
    { 
     if (interval < 0f || Single.IsNaN(interval)) 
      throw new ArgumentOutOfRangeException(nameof(interval)); 
     this.interval = interval; 
    } 

    // The interval in milliseconds. Fractions are allowed so 0.001 is one microsecond. 
    public float Interval 
    { 
     get { return interval; } 
     set 
     { 
      if (value < 0f || Single.IsNaN(value)) 
       throw new ArgumentOutOfRangeException(nameof(value)); 
      interval = value; 
     } 
    } 

    public bool Enabled 
    { 
     set 
     { 
      if (value) 
       Start(); 
      else 
       Stop(); 
     } 
     get { return isRunning; } 
    } 

    public void Start() 
    { 
     if (isRunning) 
      return; 

     isRunning = true; 
     Thread thread = new Thread(ExecuteTimer); 
     thread.Priority = ThreadPriority.Highest; 
     thread.Start(); 
    } 

    public void Stop() 
    { 
     isRunning = false; 
    } 

    private void ExecuteTimer() 
    { 
     float nextTrigger = 0f; 

     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 

     while (isRunning) 
     { 
      nextTrigger += interval; 
      float elapsed; 

      while (true) 
      { 
       elapsed = ElapsedHiRes(stopwatch); 
       float diff = nextTrigger - elapsed; 
       if (diff <= 0f) 
        break; 

       if (diff < 1f) 
        Thread.SpinWait(10); 
       else if (diff < 5f) 
        Thread.SpinWait(100); 
       else if (diff < 15f) 
        Thread.Sleep(1); 
       else 
        Thread.Sleep(10); 

       if (!isRunning) 
        return; 
      } 


      float delay = elapsed - nextTrigger; 
      Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay)); 

      // restarting the timer in every hour to prevent precision problems 
      if (stopwatch.Elapsed.TotalHours >= 1d) 
      { 
       stopwatch.Restart(); 
       nextTrigger = 0f; 
      } 
     } 

     stopwatch.Stop(); 
    } 

    private static float ElapsedHiRes(Stopwatch stopwatch) 
    { 
     return stopwatch.ElapsedTicks * tickFrequency; 
    } 
} 

public class HiResTimerElapsedEventArgs : EventArgs 
{ 
    public float Delay { get; } 

    internal HiResTimerElapsedEventArgs(float delay) 
    { 
     Delay = delay; 
    } 
} 
+0

次に、モノの実装はタイマーに約1msの分解能を与える方法を教えてください。そして、なぜ、通常の.NETフレームワークは、そのようなソリューションを使用していないのですか? – Size43

+0

それは私が言うことができないものです。 IMHOのすべてのタイマー実装は、それも提供する必要があります(System.Timers.Timer、System.Threading.Timer、System.Windows.Forms.Timer)。しかし、あなたが特定の精度を超えてそれらに頼ることができないならば、問題はなぜではなく、あなたのニーズをどのように達成できるかです。 – taffer

+0

Heh。これは奇妙なものです。あなたは階層化されたSleep()も必要ありません。私は、WaitOneだけを使用して、すべてのプラットフォームでモノタイマと同じ精度を持つ簡単な実装を書いています:http://pastebin.com/58QbCQVZ – Size43

関連する問題