Loading

C# 消除累计误差的倒计时

使用 C# 中自带的各种 timer 计时,都会有累计误差,以下代码实现了一种消除累计误差的方法,使得每次计时的误差,空值在 100 ms 以内(可以通过修改代码提升精度。)
对于精度要求在秒级别的简单计时应用来说,误差可接受,并且消除累计误差。

以下是代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace Xxx.Utils
{
    /// <summary>
    /// 带有校准功能的秒钟计时器(误差最大100ms)
    /// </summary>
    public class ClockTimer : IDisposable
    {
        private readonly Timer _driveTimer;
        private int _intervalSeconds = 1;
        private double _startMilliSeconds;
        private long _tickCount;
        private bool _enabled;

        public ClockTimer()
        {
            _driveTimer = new Timer(100);
            _driveTimer.Elapsed += DriveTimerOnElapsed;
        }

        public ClockTimer(int intervalSeconds) : this()
        {
            _intervalSeconds = intervalSeconds;
        }

        /// <summary>
        /// 获取或设置<see cref="ClockTimer"/>的触发时间间隔,单位:秒。
        /// </summary>
        public int IntervalSeconds
        {
            get => _intervalSeconds;
            set => _intervalSeconds = value < 1 ? 1 : value;
        }

        /// <summary>
        /// 获取或设置一个值,该值指示<see cref="ClockTimer"/>是否引发<see cref="Elapsed"/>事件。
        /// </summary>
        public bool Enabled
        {
            get => _enabled;
            set
            {
                if (value)
                {
                    Start();
                }
                else
                {
                    Stop();
                }
            }
        }

        /// <summary>
        /// 到达时间间隔时发生。
        /// </summary>
        public event EventHandler<ElapsedEventArgs> Elapsed;

        /// <summary>
        /// 开始计时
        /// </summary>
        public void Start()
        {
            _driveTimer.Start();
            _enabled = true;
            _startMilliSeconds = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;
        }

        /// <summary>
        /// 结束计时
        /// </summary>
        public void Stop()
        {
            _driveTimer.Stop();
            _tickCount = 0;
            _enabled = false;
        }

        private void DriveTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
        {
            double currentMilliseconds = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;

            // 第一个 100 ms,直接返回。
            if (_tickCount == 0 && Math.Abs(currentMilliseconds - _startMilliSeconds) < 100)
            {
                return;
            }

            if (Math.Abs(currentMilliseconds - (_startMilliSeconds + (_tickCount + 1) * 1000)) <= 100)
            {
                _tickCount++;
                if (_tickCount % IntervalSeconds == 0)
                {
                    Elapsed?.Invoke(this, elapsedEventArgs);
                }
            }
        }

        public void Dispose()
        {
            _driveTimer?.Dispose();
        }
    }
}
posted @ 2019-02-23 15:31  J.晒太阳的猫  阅读(778)  评论(1编辑  收藏  举报