Search code examples
c#multithreadingtimer

Single threaded timer


I wanted a timer with the following properties:

  1. No matter how many times start is called, only one call back thread is ever running

  2. The time spent in the call back function was ignored with regards to the interval. E.g if the interval is 100ms and the call back takes 4000ms to execute, the callback is called at 100ms, 4100ms etc.

I couldn't see anything available so wrote the following code. Is there a better way to do this?

/**
 * Will ensure that only one thread is ever in the callback
 */
public class SingleThreadedTimer : Timer
{
    protected static readonly object InstanceLock = new object();
    
    //used to check whether timer has been disposed while in call back
    protected bool running = false;

    virtual new public void Start()
    {
        lock (InstanceLock)
        {
            this.AutoReset = false;
            this.Elapsed -= new ElapsedEventHandler(SingleThreadedTimer_Elapsed);
            this.Elapsed += new ElapsedEventHandler(SingleThreadedTimer_Elapsed);
            this.running = true;
            base.Start();
        }
        
    }

    virtual public void SingleThreadedTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        lock (InstanceLock)
        {
            DoSomethingCool();

            //check if stopped while we were waiting for the lock,
            //we don't want to restart if this is the case..
            if (running)
            {
                this.Start();
            }
        }
    }

    virtual new public void Stop()
    {
        lock (InstanceLock)
        {
            running = false;
            base.Stop();
        }
    }
}

Solution

  • Here's a quick example I just knocked up;

    using System.Threading;
    //...
    public class TimerExample
    {
        private System.Threading.Timer m_objTimer;
        private bool m_blnStarted;
        private readonly int m_intTickMs = 1000;
        private object m_objLockObject = new object();
    
        public TimerExample()
        {
            //Create your timer object, but don't start anything yet
            m_objTimer = new System.Threading.Timer(callback, m_objTimer, Timeout.Infinite, Timeout.Infinite);
        }
    
        public void Start()
        {
            if (!m_blnStarted)
            {
                lock (m_objLockObject)
                {
                    if (!m_blnStarted) //double check after lock to be thread safe
                    {
                        m_blnStarted = true;
    
                        //Make it start in 'm_intTickMs' milliseconds, 
                        //but don't auto callback when it's done (Timeout.Infinite)
                        m_objTimer.Change(m_intTickMs, Timeout.Infinite);
                    }
                }
            }
        }
    
        public void Stop()
        {
            lock (m_objLockObject)
            {
                m_blnStarted = false;
            }
        }
    
        private void callback(object state)
        {
            System.Diagnostics.Debug.WriteLine("callback invoked");
    
            //TODO: your code here
            Thread.Sleep(4000);
    
            //When your code has finished running, wait 'm_intTickMs' milliseconds
            //and call the callback method again, 
            //but don't auto callback (Timeout.Infinite)
            m_objTimer.Change(m_intTickMs, Timeout.Infinite);
        }
    }