Click here to Skip to main content
Click here to Skip to main content

Sliding Timer

By , 10 Jan 2014
Rate this:
Please Sign up or sign in to vote.

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.

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.

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.

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.

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)

About the Author

Tomas Takac
Software Developer (Senior)
Czech Republic Czech Republic
.NET developer since 2002

Comments and Discussions

 
Generalit is useful PinmemberSouthmountain10-Jan-14 5:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 10 Jan 2014
Article Copyright 2014 by Tomas Takac
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid