Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Sliding Timer

5.00/5 (1 vote)
10 Jan 2014CPOL2 min read 13.5K   201  
How to run an action after a quiet period

Introduction

This tip shows how you can schedule an action to be run after a quiet period in case you are listening to an event which is raised frequently.

Background

I had this problem recently when developing a plugin. Our code was tracking changes in an object graph and doing some calculations based on the new state. The problem was that the event was raised in batches as the changes were cascaded through the graph. To prevent unnecessary re-calculations, we decided to wait until change propagates through the graph and all change events are fired. Only after a quiet period, the handler is invoked.

One way to do it is to use a timer and then stop and restart it each time a notification arrives. When looking at the implementation of System.Timers.Timer and System.Threading.Timer (the former uses the latter internally), I’ve got the impression that a timer is too heavyweight for this task. Also to make timer stop reliably requires further effort. But the same can be achieved simply by using a lock, a thread and Monitor class.

The Code

We create a class that will act as a timer with the ability to restart the countdown when notification arrives. Let’s assume the following declarations.

C#
private int _timeout; // initialized in constructor
private object _lock = new Object();
private bool _isRunning = false;
public event EventHandler TimeoutElapsed;

On each change, we will call a method that will start or restart the timer respectively.

C#
public void Ping()
{
    lock (_lock)
    {
        if (_isRunning)
        {
            Monitor.Pulse(_lock);
        }
        else
        {
            _isRunning = true;
            Task.Factory.StartNew(WaitForTimeout);
        }
    }
}

When timer is already started, pulse the lock to reset the timer. Otherwise, start a background thread that will wait until timeout expires.

C#
private void WaitForTimeout()
{
    lock (_lock)
    {
        while (Monitor.Wait(_lock, _timeout)) ;
        _isRunning = false;
    }

    RaiseTimeoutElapsed();
}

The background thread waits on the lock for a specified time. If the lock is pulsed and the thread awakes before timeout has expired, it simply goes to sleep again. Otherwise if the thread is not awakened prematurely and timeout expires, it raises an event.

C#
private void RaiseTimeoutElapsed()
{
    var handler = TimeoutElapsed;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    } 
}

Points of Interest

This is not reliable in the sense that there is no guarantee that the gap between events in the same logical batch will not exceed the given timeout. It’s up to you to choose proper timeout value.

If you plan to update UI in the callback, don't forget that it is fired on background thread. You need to marshal the update to the main thread.

If there is an exception thrown in the handler, it will be confined to the background thread and eventually end up in TaskScheduler.UnobservedTaskException. You may want to add some exception handling code to RaiseTimeoutElapsed() method as well.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)