Sign in to follow this  
Giawa

1ms Accuracy Timers in .NET

Recommended Posts

Hi everyone, I've struggled with the accuracy of the .NET timers in my graphics engine for the past while. I'm not talking about measuring the time between point A and point B. That can be achieved by using the StopWatch class. I wanted to use a System.Timers object that can call an event every XXms. I found that the .NET timers were only accurate to approximately 16ms on my machine. This lack of accuracy lead to a camera jitter when panning in scenes. Sometimes the camera would move every 16ms, and sometimes every 32ms. I've attempted to solve this problem by writing a custom Timer class. Please let me know what you think, and if I've completely lost my mind. However, initial tests show that this method is accurate to within +/- 1ms. Delegate and EventArgs
    public delegate void OnTimerTick(object sender, ElapsedTimerHandler e);

    public class ElapsedTimerHandler : EventArgs
    {
        public double TimePassed { get; set; }

        public bool WillRepeat { get; set; }

        public ElapsedTimerHandler(double TimePassed, bool WillRepeat)
        {
            this.TimePassed = TimePassed;
            this.WillRepeat = WillRepeat;
        }
    }



Timer class (named cTimer to avoid conflicts with System.Timers.Timer)
    public class cTimer
    {
        #region Properties
        /// <summary>
        /// The name of the timer.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// The delay (in ms) between calls to the Tick event.
        /// </summary>
        public double Delay { get; set; }

        /// <summary>
        /// The amount of time (in ms) remaining before the next Tick event.
        /// </summary>
        public double Remaining { get; set; }

        /// <summary>
        /// True if the timer should be reset after each Tick.
        /// </summary>
        public bool Repeat { get; set; }

        /// <summary>
        /// The event to call upon the completion of the Timer.
        /// </summary>
        public OnTimerTick Tick { get; set; }
        #endregion

        #region Methods
        /// <summary>
        /// Constructs a Timer object that relies on the TimerHandler to call it.
        /// </summary>
        /// <param name="Name">The unique name of this timer.</param>
        /// <param name="Delay">The time (in ms) between ticks.</param>
        /// <param name="Repeat">True to repeat the Timer, false for a one-time event.</param>
        public cTimer(string Name, double Delay, bool Repeat)
        {
            this.Name = Name;
            this.Delay = Delay;
            this.Repeat = Repeat;
        }

        /// <summary>
        /// Decrements the counter by a certain amount of time.
        /// </summary>
        /// <param name="By">Subtracts from the remaining time.</param>
        /// <returns>True if the Timer will repeat, false if the Timer should be removed.</returns>
        public bool Decrement(double By)
        {
            this.Name = Name;
            Remaining -= By;
            if (Remaining < 0)
            {
                Tick(this, new ElapsedTimerHandler(Delay - Remaining, Repeat));
                if (Repeat) Remaining += Delay;
                return true;
            }
            return false;
        }
        #endregion
    }



TimerHandler - actually contains the thread that calls the cTimer events
    public sealed class TimerHandler
    {
        #region Singleton Pattern
        /// <summary>Constructor of TimerHandler for first time instantiation</summary>
        TimerHandler()
        {
        }

        /// <summary>Gets a handle to TimerHandler (thread-safe)</summary>
        public static TimerHandler Instance
        {
            get { return Nested.instance; }
        }

        class Nested
        {
            static Nested() { }
            internal static readonly TimerHandler instance = new TimerHandler();
        }
        #endregion

        #region Methods
        internal bool Running { get; set; }

        private Thread TimerThread { get; set; }

        public List<cTimer> Timers { get; private set; }

        /// <summary>
        /// Initializes the TimerHandler.
        /// </summary>
        public void InitTimers()
        {
            Running = true;
            Timers = new List<cTimer>();
            TimerThread = new Thread(new ThreadStart(ThreadProc));
            TimerThread.Start();
        }

        /// <summary>
        /// Background thread that runs all the timers.
        /// </summary>
        public void ThreadProc()
        {
            Stopwatch sw = new Stopwatch();
            while (TimerHandler.Instance.Running)
            {
                sw.Reset();
                sw.Start();
                Thread.Sleep(1);
                sw.Stop();
                for (int i = 0; i < TimerHandler.Instance.Timers.Count; i++)
                    TimerHandler.Instance.Timers[i].Decrement((double)sw.ElapsedTicks / Stopwatch.Frequency * 1000);
            }
        }

        /// <summary>
        /// Disposes of all timers that are stored in the dictionary object.
        /// </summary>
        public void DisposeTimers()
        {
            Running = false;
            TimerThread.Abort();
            Timers.Clear();
        }

        /// <summary>
        /// Adds a single timer that will continue to execute.  It is up to the
        /// ElapsedEvent to remove the timer if the timer should only run once.
        /// </summary>
        /// <param name="Name">Unique name of the timer.</param>
        /// <param name="Delay">Delay (in ms) until ElapsedEvent is called.</param>
        /// <param name="ElapsedEvent">The method to call when the timer ticks.</param>
        public bool AddTimer(string Name, double Delay, OnTimerTick ElapsedEvent)
        {
            try
            {
                cTimer t_timer = new cTimer(Name, Delay, true);
                t_timer.Tick += ElapsedEvent;
                Timers.Add(t_timer);
                return true;
            }
            catch (Exception e)
            {
                Logger.Instance.WriteLine(WarningLevel.Error, "Could not add timer " + Name + " due to error: " + e.Message);
                return false;
            }
        }

        /// <summary>
        /// Removes a single timer from the TimerHandler.
        /// </summary>
        /// <param name="Name">Unique name of the timer.</param>
        public void RemoveTimer(string Name)
        {
            for (int i = 0; i < Timers.Count; i++)
                Timers.RemoveAt(i);
        }
        #endregion
    }



Any comments or suggestions on how to make this better? Also - is there already this functionality in C# and I totally missed it? Thanks, Giawa Edit: It might be worth noting that I only plan on having 3 or so timers running concurrently, so I used a List<cTimer> instead of something like a Dictionary<string, cTimer> or Hashtable... This code could be easily changed to use either. Edit^2: It's worth noting that it is possible to lose time in these lines of code.
for (int i = 0; i < TimerHandler.Instance.Timers.Count; i++)
                    TimerHandler.Instance.Timers[i].Decrement((double)sw.ElapsedTicks / Stopwatch.Frequency * 1000);

(as well as the loop condition check and stopwatch reset) However I ran this for a 120s timer with 100% processor utilization and the largest discrepancy I saw was 2ms. So for short timers (which is what I wanted) this seems to work well. [Edited by - Giawa on March 13, 2009 9:30:21 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this