System.Timers.Timerの精度
WEBで検索すればすぐ出てきますが、System.Timers.Timer
の精度は数10msです。
誤差があるのは分かっているので それはいいのですが、
この誤差、0を中心に±両方向に分布していて平均すると0付近になるのかな~と思ったら
往々にして+方向にズレ(環境によると思いますが)、且つ誤差は蓄積していくようです。
以下はInterval=1000
にして1秒毎にDateTime.Now
の時間を表示してみた結果です。
自宅のPCだと1ms程度の誤差ですが、会社のPCでは20ms程度の誤差が蓄積していきました。
2015/11/23 22:14:31.429
2015/11/23 22:14:32.429
2015/11/23 22:14:33.430
2015/11/23 22:14:34.430
2015/11/23 22:14:35.433
2015/11/23 22:14:36.434
2015/11/23 22:14:37.434
2015/11/23 22:14:38.435
2015/11/23 22:14:39.435
2015/11/23 22:14:40.436
2015/11/23 22:14:41.436
2015/11/23 22:14:42.437
2015/11/23 22:14:43.437
2015/11/23 22:14:44.437
2015/11/23 22:14:45.437
2015/11/23 22:14:46.438
2015/11/23 22:14:47.438
2015/11/23 22:14:48.438
2015/11/23 22:14:49.439
2015/11/23 22:14:50.440
これだと都合が悪い場合があったので、どうしたもんか…と考えた結果
このようなwrapperを作ってみました。
namespace Utils
{
public class MyTimer
{
private System.Timers.Timer timer;
private Int64 lastTime;
public Boolean AutoReset;
public Double Interval;
public event System.Timers.ElapsedEventHandler Elapsed;
public MyTimer()
{
this.timer = new System.Timers.Timer();
this.timer.AutoReset = true;
this.timer.Interval = 1;
this.timer.Elapsed += (o, e) =>
{
if ((DateTime.Now.Ticks / 10000 - this.lastTime) >= this.Interval)
{
System.Threading.ThreadPool.QueueUserWorkItem((state) => this.Elapsed(o, e));
this.lastTime += (Int64)this.Interval;
if (!this.AutoReset)
{
this.timer.Stop();
}
}
};
}
public MyTimer(Double interval) : this()
{
this.Interval = interval;
}
public void Start()
{
this.lastTime = DateTime.Now.Ticks / 10000;
this.timer.Start();
}
public void Stop()
{
this.timer.Stop();
}
}
}
本来設定したいInterval
より充分短い間隔で経過時間を観測し、
期待する時間が経過したらElapsed
に設定された処理をするだけですが、
誤差が蓄積せずループを回すことができました。
Utils.MyTimer
を使った場合の結果
2015/11/23 22:16:23.806
2015/11/23 22:16:24.806
2015/11/23 22:16:25.805
2015/11/23 22:16:26.806
2015/11/23 22:16:27.806
2015/11/23 22:16:28.805
2015/11/23 22:16:29.805
2015/11/23 22:16:30.806
2015/11/23 22:16:31.806
2015/11/23 22:16:32.805
2015/11/23 22:16:33.806
2015/11/23 22:16:34.805
2015/11/23 22:16:35.805
2015/11/23 22:16:36.806
2015/11/23 22:16:37.806
2015/11/23 22:16:38.805
2015/11/23 22:16:39.806
2015/11/23 22:16:40.805
2015/11/23 22:16:41.806
2015/11/23 22:16:42.805
もっと精度よくループを回したければSystem.Timers.Timer
で経過時間を監視しているところを別Threadで監視するとか、
経過時間取得部分をSystem.Diagnostics.Stopwatch
で取る
(高分解能パフォーマンスカウンタが使用可能な場合は内部的にQueryPerformanceCounter
使われるらしいので)
などすれば良いかも。
ただ、自分の環境ではSystem.Diagnostics.Stopwatch
の使用ではそんなに良い結果を得られませんでした。
まぁDateTime.Now
の精度がどうなの?って感じで、何を測ってるのか分からないのでアレですね。
今回はこれで自分が求める結果が得られたので良しとします。