7

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();
        }
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
probably at the beach
  • 14,489
  • 16
  • 75
  • 116
  • Somewhat related: [Run async method regularly with specified interval](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval) – Theodor Zoulias Nov 14 '21 at 18:19

5 Answers5

9

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);
    }
}
firefox1986
  • 1,602
  • 11
  • 9
  • Good answer and thanks. I was wandering what would would happen if two threads called the Start routine simultaneously? – probably at the beach Mar 31 '11 at 13:28
  • @Richard Fair point, the answer would reliably work only for single a threaded application. I've edited my answer to give an idea of where locking would come into play. You could always lock(this) instead if you prefer. – firefox1986 Mar 31 '11 at 13:48
  • @Richard: To ensure only one thread will be calling the start routine at a given time you can use Mutex. Further details could be found here http://msdn.microsoft.com/en-us/library/ms173179.aspx – MUS Mar 31 '11 at 13:51
4

The .NET Framework provides four timers. Two of these are general-purpose multithreaded timers:

  • System.Threading.Timer
  • System.Timers.Timer

The other two are special-purpose single-threaded timers:

  • System.Windows.Forms.Timer (Windows Forms timer)
  • System.Windows.Threading.DispatcherTimer (WPF timer)

The last 2 are designed to eliminate thread-safety issues for WPF and Windows Forms applications.

For example, using WebBrowser inside a timer to capture screenshots from webpage needs to be single-threaded and gives an error at runtime if it is on another thread.

The single-thread timers have the following benefits

  • You can forget about thread safety.
  • A fresh Tick will never fire until the previous Tick has finished processing.
  • You can update user interface elements and controls directly from Tick event handling code, without calling Control.BeginInvoke or Dispatcher.BeginIn voke.

and main disadvantage to note

  • One thread serves all timers—as well as the processing UI events. Which means that the Tick event handler must execute quickly, otherwise the user interface becomes unresponsive.

source: most are scraps from C# in a Nutshell book -> Chapter 22 -> Advanced threading -> Timers -> Single-Threaded Timers

Iman
  • 17,932
  • 6
  • 80
  • 90
4

For anyone who needs a single thread timer and wants the timer start to tick after task done. System.Timers.Timer could do the trick without locking or [ThreadStatic]

System.Timers.Timer tmr;

void InitTimer(){
    tmr = new System.Timers.Timer();
    tmr.Interval = 300;
    tmr.AutoReset = false;
    tmr.Elapsed += OnElapsed;
}

void OnElapsed( object sender, System.Timers.ElapsedEventArgs e )
{
    backgroundWorking();

    // let timer start ticking
    tmr.Enabled = true;
}

Credit to Alan N source https://www.codeproject.com/Answers/405715/System-Timers-Timer-single-threaded-usage#answer2

Edit: spacing

Louis Go
  • 2,213
  • 2
  • 16
  • 29
1

Look at the [ThreadStatic] attribute and the .Net 4.0 ThreadLocal generic type. This will probably quickly give you a way to code this without messing with thread locking etc.

You could have a stack inside your time class, and you could implement a Monitor() method that returns a IDisposable, so you can use the timer like so:

using (_threadTimer.Monitor())
{
     // do stuff
}

Have the timer-monitor pop the the interval timestamp off the stack during Dispose().

Manually coding all the locking and thread recognition is an option as has been mentioned. However, locking will influence the time used, most likely more than having to initialize an instance per thread using ThreadLocal

If you're interested, I might knock up an example later

sehe
  • 374,641
  • 47
  • 450
  • 633
1

Here is a simple PeriodicNonOverlappingTimer class, that provides just the requested features, and nothing more than that. This timer cannot be started and stopped on demand, and neither can have its interval changed. It just invokes the specified action periodically in a non overlapping manner, until the timer is disposed.

/// <summary>
/// Invokes an action on the ThreadPool at specified intervals, ensuring
/// that the invocations will not overlap, until the timer is disposed.
/// </summary>
public class PeriodicNonOverlappingTimer : IDisposable, IAsyncDisposable
{
    private readonly System.Threading.Timer _timer;

    public PeriodicNonOverlappingTimer(Action periodicAction,
        TimeSpan dueTime, TimeSpan period)
    {
        // Arguments validation omitted
        _timer = new(_ =>
        {
            var stopwatch = Stopwatch.StartNew();
            periodicAction();
            var nextDueTime = period - stopwatch.Elapsed;
            if (nextDueTime < TimeSpan.Zero) nextDueTime = TimeSpan.Zero;
            try { _timer.Change(nextDueTime, Timeout.InfiniteTimeSpan); }
            catch (ObjectDisposedException) { } // Ignore this exception
        });
        _timer.Change(dueTime, Timeout.InfiniteTimeSpan);
    }

    public void Dispose() => _timer.DisposeAsync().AsTask().Wait();
    public ValueTask DisposeAsync() => _timer.DisposeAsync();
}

Usage example. Shows how to create a non-overlapping timer that starts immediately, with a period of 10 seconds.

var timer = new PeriodicNonOverlappingTimer(() =>
{
    DoSomethingCool();
}, TimeSpan.Zero, TimeSpan.FromSeconds(10));

//...

timer.Dispose(); // Stop the timer once and for all

In case the DoSomethingCool fails, the exception will be thrown on the ThreadPool, causing the process to crash. So you may want to add a try/catch block, and handle all the exceptions that may occur.

The Dispose is a potentially blocking method. If the periodicAction is currently running, the Dispose will block until the last invocation is completed. If you don't want to wait for this to happen, you can do this instead:

_ = timer.DisposeAsync(); // Stop the timer without waiting it to finish
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104