25
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Timerを使ったループで誤差が蓄積しないようにする

Posted at

System.Timers.Timerの精度

WEBで検索すればすぐ出てきますが、System.Timers.Timerの精度は数10msです。
誤差があるのは分かっているので それはいいのですが、
この誤差、0を中心に±両方向に分布していて平均すると0付近になるのかな~と思ったら
往々にして+方向にズレ(環境によると思いますが)、且つ誤差は蓄積していくようです。
以下はInterval=1000にして1秒毎にDateTime.Nowの時間を表示してみた結果です。
自宅のPCだと1ms程度の誤差ですが、会社のPCでは20ms程度の誤差が蓄積していきました。

before
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を作ってみました。

MyTimer.cs
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を使った場合の結果

after
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の精度がどうなの?って感じで、何を測ってるのか分からないのでアレですね。

今回はこれで自分が求める結果が得られたので良しとします。

25
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?