X

How to avoid overlapping timer calls?

Timers are useful .NET feature. With timers we can execute code with specified interval. With real scenarios we can easily face situations where timer calls its callback again but previous call is not finished yet. It may end up with loading some external system too much. It may also end up by making unexpected parallel data processing that distorts the results. This post introduces some options to avoid overlapping timer calls.

Let’s start with simple class that hosts timer that fires callback after every ten seconds.

public class TimerProcess
{
    private static Timer _timer;

    public void Start()
    {
        _timer = new Timer(Callback, null, 0, 10000);
    }

    public void Stop()
    {
        _timer.Dispose();
    }

    public void Callback(object state)
    {
        // Do something
    }
}

We have no controls for overlapping calls here. If callback is not finished after ten seconds then next next call to it is made by timer in parallel.

Using lock

If we want to avoid overlapping then first and simplest idea to come out with is locking the part of callback body where actual work is done. For this we use lock statement.

public class TimerProcess
{
    private static object _locker = new object();
    private static Timer _timer;

    public void Start()
    {
        _timer = new Timer(Callback, null, 3000, 2000);
    }

    public void Stop()
    {
        _timer.Dispose();
    }

    public void Callback(object state)
    {
         lock(_locker)
         {
            // Do something
         }
    }
}

There are no overlapping calls anymore but there is still one problem – if callback takes minute to execute for some unexpected reason then there will be six calls waiting after lock. As soon as lock is released the next call that successfully acquires lock starts executing lock body.

Control locks using Monitor class

To avoid callback calls forming a long waiting row we can use Monitor class to jump out from calls that failed to acquire lock.

Lock in C# is actually syntactic sugar that transforms to try-finally block that hosts Monitor class. To get better idea how things work internally, please read my blog post Are lock and Monitor the same in C#? The sample below uses Monitor.TryEnter() instead of Monitor.Enter() to acquire lock.

public class TimerProcess
{
    private static object _locker = new object();
    private static Timer _timer;

    public void Start()
    {
        _timer = new Timer(Callback, null, 0, 10000);
    }

    public void Stop()
    {
        _timer.Dispose();
    }

    public void Callback(object state)
    {
        var hasLock = false;

        try
        {
            Monitor.TryEnter(_locker, ref hasLock);
            if (!hasLock)
            {
                return;
            }

            // Do something
        }
        finally
        {
            if (hasLock)
            {
                Monitor.Exit(_locker);
            }
        }
    }
}

Now we almost got rid of overlapping calls but not completely. Calls to timer callback still happen but they quit fast when failing to acquire lock. This method is also good for cases when some work in timer callback must be done at every call and there’s also some work that can’t be done in parallel by multiple threads.

Stopping timer while callback is running

We can get rid of those “empty” calls introduced in previous point by stopping timer while callback is running. For this we change timer start time and interval to Timeout.Infinite.

public class TimerProcess
{
    private const long TimerInterval = 10000;
    private static object _locker = new object();
    private static Timer _timer;

    public void Start()
    {
        _timer = new Timer(Callback, null, 0, TimerInterval);
    }

    public void Stop()
    {
        _timer.Dispose();
    }

    public void Callback(object state)
    {
        var hasLock = false;

        try
        {
            Monitor.TryEnter(_locker, ref hasLock);
            if (!hasLock)
            {
                return;
            }
             _timer.Change(Timeout.Infinite, Timeout.Infinite);
            // Do something
        }
        finally
        {
            if (hasLock)
            {
                Monitor.Exit(_locker);
                 _timer.Change(TimerInterval, TimerInterval);
            }
        }
    }
}

The code above stops timer before work is made in callback. When code in try block is executed then call in finally block is called. I there was successfully acquired lock then we have to activate timer again. We let it wait ten seconds before going to next round and its interval will also be ten seconds. Before using this method be sure that it fits your scenario and running timer exactly after given interval is not hard requirement.

Wrapping up

Overlapping timer callbacks may cause a lot of trouble in system. There are few ways how to avoid it and here were three of these shown. Using lock statement is the easiest way to go but not very performant under unexpected situations. Using Monitor class directly is more complex but we win in performance. If we want to avoid overlapping timer callbacks maximally, we can stop timer while callback is running. None of these methods is the one and only correct solution. It always depends on requirements and many other factors.

Liked this post? Empower your friends by sharing it!
Categories: .NET

View Comments (4)

  • Or use a fire-once timer
    _pollTimer = New System.Threading.Timer(AddressOf _pollTimer_Elapsed, Nothing, 0, Timeout.Infinite)

    and restart it in its callback routine
    _pollTimer.Change(_pollInterval, Timeout.InfiniteTimeSpan)

  • It would be interesting to see a Task-based solution to this problem instead of Timer-based to see how they compare. To be honest, the Timer-based with the Monitor and locking looks a bit hard to read. But TPL is not available everywhere (e.g. some embedded devices), so there's not always a choice.

    Also, note that there are three different Timer classes in .NET Framework:

    System.Windows.Forms.Timer
    System.Timers.Timer
    System.Threading.Timer

    It looks like you're using the System.Threading.Timer one, but it wouldn't hurt to be explicit (and e.g. include a namespace import) to avoid ambiguity.

  • Your approach will risk when whe need call async method. Instead of use Monitor, we can use Interlocked

    private int _syncPoint = 0;
    public async void Callback(object state)
    {
    int sync = Interlocked.CompareExchange(ref _syncPoint, 1, 0);

    if (sync == 0)
    {
    try
    {
    _timer.Change(Timeout.Infinite, Timeout.Infinite);

    // Do something await ...
    }
    catch (Exception ex)
    {
    Logger.LogError(ex, $"{GetType().Name} EXCEPTION!");
    }
    finally
    {
    _syncPoint = 0;
    _timer.Change(TimerInterval, TimerInterval);
    }
    }
    }

Related Post