65

I'm listening to a hardware event message, but I need to debounce it to avoid too many queries.

This is an hardware event that sends the machine status and I have to store it in a database for statistical purposes, and it sometimes happens that its status changes very often (flickering?). In this case I would like to store only a "stable" status and I want to implement it by simply waiting for 1-2s before storing the status to the database.

This is my code:

private MachineClass connect()
{
    try
    {
        MachineClass rpc = new MachineClass();
        rpc.RxVARxH += eventRxVARxH;
        return rpc;
    }
    catch (Exception e1)
    {
        log.Error(e1.Message);
        return null;
    }
}

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
}

I call this behaviour "debounce": wait a few times to really do its job: if the same event is fired again during the debounce time, I have to dismiss the first request and start to wait the debounce time to complete the second event.

What is the best choice to manage it? Simply a one-shot timer?

To explain the "debounce" function please see this javascript implementation for key events: http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/

6
  • Use a stopwatch to measure the time which has passed. Commented Feb 12, 2015 at 8:02
  • "I have to dismiss the first request" - that sounds very problematic. Is there a specific reason why the first request can't be allowed to proceed and the subsequent one ignored? Commented Feb 12, 2015 at 8:04
  • This is an hardware event that sends the machine satus and I have to store it in a database for statistic purposes, sometimes happends that its status changes very often (flickering?), in this case I would like to store only a "stable" status and I want to implement it simply waiting for 1-2s before storing the status to database. Maybe I can do it with a timer? I wait 1-2s to fire the query resetting it in case of too close status change.
    – Tobia
    Commented Feb 12, 2015 at 8:09
  • 1
    @Damien_The_Unbeliever this isn't a strange request nor is it trivial. It can't be handled with just a simple timer. Reactive Extensions do cover this scenario though. Commented Feb 12, 2015 at 8:13
  • 1
    A very similar problem is waiting for a FileSystemWatcher to stop reporting changes eg. while copying a large file. You get a lot of change events but really want to wait a bit after the last event before trying to access the modified file. Commented Feb 12, 2015 at 8:14

23 Answers 23

64

I've used this to debounce events with some success:

public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
    var last = 0;
    return arg =>
    {
        var current = Interlocked.Increment(ref last);
        Task.Delay(milliseconds).ContinueWith(task =>
        {
            if (current == last) func(arg);
            task.Dispose();
        });
    };
}

Usage

Action<int> a = (arg) =>
{
    // This was successfully debounced...
    Console.WriteLine(arg);
};
var debouncedWrapper = a.Debounce<int>();

while (true)
{
    var rndVal = rnd.Next(400);
    Thread.Sleep(rndVal);
    debouncedWrapper(rndVal);
}

It may not be a robust as what's in RX but it's easy to understand and use.

Followup 2020-02-03

Revised @collie's solution using cancellation tokens as follows

public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
    CancellationTokenSource? cancelTokenSource = null;

    return arg =>
    {
        cancelTokenSource?.Cancel();
        cancelTokenSource = new CancellationTokenSource();

        Task.Delay(milliseconds, cancelTokenSource.Token)
            .ContinueWith(t =>
            {
                if (t.IsCompletedSuccessfully)
                {
                    func(arg);
                }
            }, TaskScheduler.Default);
    };
}

Notes:

  • Calling Cancel is enough to dispose of the CTS
  • A successfully completed CTS is not canceled/disposed until the next call
  • As noted by @collie, tasks get disposed so no need to call Dispose on the task

I've not worked with cancellation tokens before and may not be using them correctly.

6
  • 1
    How did you use it? Commented Feb 14, 2017 at 22:28
  • 7
    Slick, it took me some time to notice how do you cancel already "running" action :-). However this approach has a problem, you don't have a feedback/control over your debouncer, so you don't control when all the actions finish. This is troublesome, when for example you dispose your main object, and you don't realize the debounced action will be executed after disposal. Commented Sep 27, 2018 at 10:11
  • 2
    See stackoverflow.com/a/59296962/545233 for a version that uses cancellation tokens to clean up the unused tasks.
    – Collie
    Commented Dec 12, 2019 at 3:38
  • 8
    IsCompletedSuccessfully is only available in .NET Core. You could might use !t.IsCanceled instead to make the code also work in .NET Framework.
    – PEK
    Commented Feb 22, 2020 at 12:16
  • @PEK it's available in .NET Standard 2.1 and all of .NET Core up to .NET 7.0
    – Max
    Commented Jul 7, 2023 at 22:32
59

This isn't a trivial request to code from scratch as there are several nuances. A similar scenario is monitoring a FileSystemWatcher and waiting for things to quiet down after a big copy, before you try to open the modified files.

Reactive Extensions in .NET 4.5 were created to handle exactly these scenarios. You can use them easily to provide such functionality with methods like Throttle, Buffer, Window or Sample. You post the events to a Subject, apply one of the windowing functions to it, for example to get a notification only if there was no activity for X seconds or Y events, then subscribe to the notification.

Subject<MyEventData> _mySubject=new Subject<MyEventData>();
....
var eventSequenc=mySubject.Throttle(TimeSpan.FromSeconds(1))
                          .Subscribe(events=>MySubscriptionMethod(events));

Throttle returns the last event in a sliding window, only if there were no other events in the window. Any event resets the window.

You can find a very good overview of the time-shifted functions here

When your code receives the event, you only need to post it to the Subject with OnNext:

_mySubject.OnNext(MyEventData);

If your hardware event surfaces as a typical .NET Event, you can bypass the Subject and manual posting with Observable.FromEventPattern, as shown here:

var mySequence = Observable.FromEventPattern<MyEventData>(
    h => _myDevice.MyEvent += h,
    h => _myDevice.MyEvent -= h);  
_mySequence.Throttle(TimeSpan.FromSeconds(1))
           .Subscribe(events=>MySubscriptionMethod(events));

You can also create observables from Tasks, combine event sequences with LINQ operators to request eg: pairs of different hardware events with Zip, use another event source to bound Throttle/Buffer etc, add delays and a lot more.

Reactive Extensions is available as a NuGet package, so it's very easy to add them to your project.

Stephen Cleary's book "Concurrency in C# Cookbook" is a very good resource on Reactive Extensions among other things, and explains how you can use it and how it fits with the rest of the concurrent APIs in .NET like Tasks, Events etc.

Introduction to Rx is an excellent series of articles (that's where I copied the samples from), with several examples.

UPDATE

Using your specific example, you could do something like:

IObservable<MachineClass> _myObservable;

private MachineClass connect()
{

    MachineClass rpc = new MachineClass();
   _myObservable=Observable
                 .FromEventPattern<MachineClass>(
                            h=> rpc.RxVARxH += h,
                            h=> rpc.RxVARxH -= h)
                 .Throttle(TimeSpan.FromSeconds(1));
   _myObservable.Subscribe(machine=>eventRxVARxH(machine));
    return rpc;
}

This can be improved vastly of course - both the observable and the subscription need to be disposed at some point. This code assumes that you only control a single device. If you have many devices, you could create the observable inside the class so that each MachineClass exposes and disposes its own observable.

3
  • 1
    This seems to be the answer! Thank you... but It is not so easy to implement, and I can not understand how to apply your example to my code.
    – Tobia
    Commented Feb 12, 2015 at 14:08
  • Thanks, I get what I missed before!
    – Tobia
    Commented Feb 12, 2015 at 14:42
  • I updated the nuget link since the old Rx-Main package is unlisted and superseeded. But some of the documentation links also are in need of an update.
    – hlovdal
    Commented Apr 25, 2022 at 14:26
14

Recently I was doing some maintenance on an application that was targeting an older version of the .NET framework (v3.5).

I couldn't use Reactive Extensions nor Task Parallel Library, but I needed a nice, clean, consistent way of debouncing events. Here's what I came up with:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace MyApplication
{
    public class Debouncer : IDisposable
    {
        readonly TimeSpan _ts;
        readonly Action _action;
        readonly HashSet<ManualResetEvent> _resets = new HashSet<ManualResetEvent>();
        readonly object _mutex = new object();

        public Debouncer(TimeSpan timespan, Action action)
        {
            _ts = timespan;
            _action = action;
        }

        public void Invoke()
        {
            var thisReset = new ManualResetEvent(false);

            lock (_mutex)
            {
                while (_resets.Count > 0)
                {
                    var otherReset = _resets.First();
                    _resets.Remove(otherReset);
                    otherReset.Set();
                }

                _resets.Add(thisReset);
            }

            ThreadPool.QueueUserWorkItem(_ =>
            {
                try
                {
                    if (!thisReset.WaitOne(_ts))
                    {
                        _action();
                    }
                }
                finally
                {
                    lock (_mutex)
                    {
                        using (thisReset)
                            _resets.Remove(thisReset);
                    }
                }
            });
        }

        public void Dispose()
        {
            lock (_mutex)
            {
                while (_resets.Count > 0)
                {
                    var reset = _resets.First();
                    _resets.Remove(reset);
                    reset.Set();
                }
            }
        }
    }
}

Here's an example of using it in a windows form that has a search text box:

public partial class Example : Form 
{
    private readonly Debouncer _searchDebouncer;

    public Example()
    {
        InitializeComponent();
        _searchDebouncer = new Debouncer(TimeSpan.FromSeconds(.75), Search);
        txtSearchText.TextChanged += txtSearchText_TextChanged;
    }

    private void txtSearchText_TextChanged(object sender, EventArgs e)
    {
        _searchDebouncer.Invoke();
    }

    private void Search()
    {
        if (InvokeRequired)
        {
            Invoke((Action)Search);
            return;
        }

        if (!string.IsNullOrEmpty(txtSearchText.Text))
        {
            // Search here
        }
    }
}
12

I ran into issues with this. I tried each of the answers here, and since I'm in a Xamarin universal app, I seem to be missing certain things that are required in each of these answers, and I didn't want to add any more packages or libraries. My solution works exactly how I'd expect it to, and I haven't run into any issues with it. Hope it helps somebody.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace OrderScanner.Models
{
    class Debouncer
    {
        private List<CancellationTokenSource> StepperCancelTokens = new List<CancellationTokenSource>();
        private int MillisecondsToWait;
        private readonly object _lockThis = new object(); // Use a locking object to prevent the debouncer to trigger again while the func is still running

        public Debouncer(int millisecondsToWait = 300)
        {
            this.MillisecondsToWait = millisecondsToWait;
        }

        public void Debouce(Action func)
        {
            CancelAllStepperTokens(); // Cancel all api requests;
            var newTokenSrc = new CancellationTokenSource();
            lock (_lockThis)
            {
                StepperCancelTokens.Add(newTokenSrc);
            }
            Task.Delay(MillisecondsToWait, newTokenSrc.Token).ContinueWith(task => // Create new request
            {
                if (!newTokenSrc.IsCancellationRequested) // if it hasn't been cancelled
                {
                    CancelAllStepperTokens(); // Cancel any that remain (there shouldn't be any)
                    StepperCancelTokens = new List<CancellationTokenSource>(); // set to new list
                    lock (_lockThis)
                    {
                        func(); // run
                    }
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void CancelAllStepperTokens()
        {
            foreach (var token in StepperCancelTokens)
            {
                if (!token.IsCancellationRequested)
                {
                    token.Cancel();
                }
            }
        }
    }
}

It's called like so...

private Debouncer StepperDeboucer = new Debouncer(1000); // one second

StepperDeboucer.Debouce(() => { WhateverMethod(args) });

I wouldn't recommend this for anything where the machine could be sending in hundreds of requests a second, but for user input, it works excellently. I'm using it on a stepper in an android/IOS app that calls to an api on step.

7
  • I don't understand how this is intended to work. It seems like if Debounce get's called more often than MillisecondsToWait milis, the code will never execute. Am I missing something?
    – jjnguy
    Commented Oct 31, 2018 at 16:27
  • Have you tried it? Works perfectly in my implementations. Just set your debounce time to something like 2000, and debug to see how it works.
    – Nieminen
    Commented Oct 31, 2018 at 17:12
  • 2
    Oh, well of course you don't get an output, it's a debounce, not a throttle. Debounce waits until input events stop for a defined amount of time before running the function. If you want a throttle (run it so many times per defined amount of time) this is not the solution you want.
    – Nieminen
    Commented Oct 31, 2018 at 18:53
  • 1
    Ok, that makes more sense. Thanks for clarifying. I completely misunderstood the goal of the code.
    – jjnguy
    Commented Oct 31, 2018 at 19:45
  • 1
    If you are using this to interact with events coming off the UI thread (as you usually would be), it's worth adding TaskScheduler.FromCurrentSynchronizationContext() to the second parameter of the .ContinueWith call. Otherwise you'll have to make another call to get threading back to the UI, which ruins performance. See: stackoverflow.com/questions/4331262/…
    – Arlo
    Commented Sep 2, 2020 at 9:09
9

I needed something like this but in a web-application, so I can't store the Action in a variable, it will be lost between http requests.

Based on other answers and @Collie idea I created a class that looks at a unique string key for throttling.

public static class Debouncer
{
    static ConcurrentDictionary<string, CancellationTokenSource> _tokens = new ConcurrentDictionary<string, CancellationTokenSource>();
    public static void Debounce(string uniqueKey, Action action, int seconds)
    {
        var token = _tokens.AddOrUpdate(uniqueKey,
            (key) => //key not found - create new
            {
                return new CancellationTokenSource();
            },
            (key, existingToken) => //key found - cancel task and recreate
            {
                existingToken.Cancel(); //cancel previous
                return new CancellationTokenSource();
            }
        );

        //schedule execution after pause
        Task.Delay(seconds * 1000, token.Token).ContinueWith(task =>
        {
            if (!task.IsCanceled)
            {
                action(); //run
                if (_tokens.TryRemove(uniqueKey, out var cts)) cts.Dispose(); //cleanup
            }
        }, token.Token);
    }
}

Usage:

//throttle for 5 secs if it's already been called with this KEY
Debouncer.Debounce("Some-Unique-ID", () => SendEmails(), 5);

As a side bonus, because it's based on a string key, you can use inline lambda's

Debouncer.Debounce("Some-Unique-ID", () => 
{
    //do some work here
}, 5);
8

RX is probably the easiest choice, especially if you're already using it in your application. But if not, adding it might be a bit of overkill.

For UI based applications (like WPF) I use the following class that use DispatcherTimer:

public class DebounceDispatcher
{
    private DispatcherTimer timer;
    private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);

    public void Debounce(int interval, Action<object> action,
        object param = null,
        DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
        Dispatcher disp = null)
    {
        // kill pending timer and pending ticks
        timer?.Stop();
        timer = null;

        if (disp == null)
            disp = Dispatcher.CurrentDispatcher;

        // timer is recreated for each event and effectively
        // resets the timeout. Action only fires after timeout has fully
        // elapsed without other events firing in between
        timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
        {
            if (timer == null)
                return;

            timer?.Stop();
            timer = null;
            action.Invoke(param);
        }, disp);

        timer.Start();
    }
}

To use it:

private DebounceDispatcher debounceTimer = new DebounceDispatcher();

private void TextSearchText_KeyUp(object sender, KeyEventArgs e)
{
    debounceTimer.Debounce(500, parm =>
    {
        Model.AppModel.Window.ShowStatus("Searching topics...");
        Model.TopicsFilter = TextSearchText.Text;
        Model.AppModel.Window.ShowStatus();
    });
}

Key events are now only processed after keyboard is idle for 200ms - any previous pending events are discarded.

There's also a Throttle method which always fires events after a given interval:

    public void Throttle(int interval, Action<object> action,
        object param = null,
        DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
        Dispatcher disp = null)
    {
        // kill pending timer and pending ticks
        timer?.Stop();
        timer = null;

        if (disp == null)
            disp = Dispatcher.CurrentDispatcher;

        var curTime = DateTime.UtcNow;

        // if timeout is not up yet - adjust timeout to fire 
        // with potentially new Action parameters           
        if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
            interval = (int) curTime.Subtract(timerStarted).TotalMilliseconds;

        timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
        {
            if (timer == null)
                return;

            timer?.Stop();
            timer = null;
            action.Invoke(param);
        }, disp);

        timer.Start();
        timerStarted = curTime;            
    }
7

This little gem is inspired by Mike Wards diabolically ingenious extension attempt. However, this one cleans up after itself quite nicely.

public static Action Debounce(this Action action, int milliseconds = 300)
{
    CancellationTokenSource lastCToken = null;

    return () =>
    {
        //Cancel/dispose previous
        lastCToken?.Cancel();
        try { 
            lastCToken?.Dispose(); 
        } catch {}          

        var tokenSrc = lastCToken = new CancellationTokenSource();

        Task.Delay(milliseconds).ContinueWith(task => { action(); }, tokenSrc.Token);
    };
}

Note: there's no need to dispose of the task in this case. See here for the evidence.

Usage

Action DebounceToConsole;
int count = 0;

void Main()
{
    //Assign
    DebounceToConsole = ((Action)ToConsole).Debounce(50);

    var random = new Random();
    for (int i = 0; i < 50; i++)
    {
        DebounceToConsole();
        Thread.Sleep(random.Next(100));
    }
}

public void ToConsole()
{
    Console.WriteLine($"I ran for the {++count} time.");
}
2
  • Neat idea. Does it need a fence around the Cancel/Dispose/new/assign part? Also, good reference about the task dispose. I always fret about zombie tasks. Guess I can fret a little less now. Thanks.
    – Mike Ward
    Commented Dec 12, 2019 at 15:02
  • Hmm. Examining the CancellationTokenSource docs, apparently it's thread-safe except for Dispose() "which must only be used when all other operations on the CancellationTokenSource object have completed." . I believe it's safe to Dispose() after Cancel() in this use case. However, they do recommend that Dispose() used in this context be wrapped in a Try-Catch. I'll add that.
    – Collie
    Commented Dec 15, 2019 at 16:27
5

Panagiotis's answer is certainly correct, however I wanted to give a simpler example, as it took me a while to sort through how to get it working. My scenario is that a user types in a search box, and as the user types we want to make api calls to return search suggestions, so we want to debounce the api calls so they don't make one every time they type a character.

I'm using Xamarin.Android, however this should apply to any C# scenario...

private Subject<string> typingSubject = new Subject<string> ();
private IDisposable typingEventSequence;

private void Init () {
            var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
            searchText.TextChanged += SearchTextChanged;
            typingEventSequence = typingSubject.Throttle (TimeSpan.FromSeconds (1))
                .Subscribe (query => suggestionsAdapter.Get (query));
}

private void SearchTextChanged (object sender, TextChangedEventArgs e) {
            var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
            typingSubject.OnNext (searchText.Text.Trim ());
        }

public override void OnDestroy () {
            if (typingEventSequence != null)
                typingEventSequence.Dispose ();
            base.OnDestroy ();
        }

When you first initialize the screen / class, you create your event to listen to the user typing (SearchTextChanged), and then also set up a throttling subscription, which is tied to the "typingSubject".

Next, in your SearchTextChanged event, you can call typingSubject.OnNext and pass in the search box's text. After the debounce period (1 second), it will call the subscribed event (suggestionsAdapter.Get in our case.)

Lastly, when the screen is closed, make sure to dispose of the subscription!

4

Created this class for solving it also for awaitable calls:

public class Debouncer
{
    private CancellationTokenSource _cancelTokenSource = null;

    public async Task Debounce(Func<Task> method, int milliseconds = 300)
    {
        _cancelTokenSource?.Cancel();
        _cancelTokenSource?.Dispose();

        _cancelTokenSource = new CancellationTokenSource();

        await Task.Delay(milliseconds, _cancelTokenSource.Token);

        await method();
    }
}

Sample of use:

private Debouncer _debouncer = new Debouncer();
....
await _debouncer.Debounce(YourAwaitableMethod);
3

This is inspired by Nieminen's Task.Delay-based Debouncer class. Simplified, some minor corrections, and should clean up after itself better.

class Debouncer: IDisposable
{
    private CancellationTokenSource lastCToken;
    private int milliseconds;

    public Debouncer(int milliseconds = 300)
    {
        this.milliseconds = milliseconds;
    }

    public void Debounce(Action action)
    {
        Cancel(lastCToken);

        var tokenSrc = lastCToken = new CancellationTokenSource();

        Task.Delay(milliseconds).ContinueWith(task =>
        {
             action();
        }, 
            tokenSrc.Token
        );
    }

    public void Cancel(CancellationTokenSource source)
    {
        if (source != null)
        {
            source.Cancel();
            source.Dispose();
        }                 
    }

    public void Dispose()
    {
        Cancel(lastCToken);
    }

    ~Debouncer()
    {
        Dispose();
    }
}

Usage

private Debouncer debouncer = new Debouncer(500); //1/2 a second
...
debouncer.Debounce(SomeAction);
2
  • This is awesome. I wrote another answer based on your solution, that uses a unique string-key for throttling (rather than actual Action) useful for webapps stackoverflow.com/a/64867741/56621 Commented Nov 17, 2020 at 10:57
  • I wrote one that is async without continuations - suitable for use in aspnet
    – lonix
    Commented Mar 5, 2021 at 13:59
3

I needed a Debounce method for Blazor and kept coming back to this page so I wanted to share my solution in case it helps others.

public class DebounceHelper
{
    private CancellationTokenSource debounceToken = null;

    public async Task DebounceAsync(Func<CancellationToken, Task> func, int milliseconds = 1000)
    {
        try
        {
            // Cancel previous task
            if (debounceToken != null) { debounceToken.Cancel(); }

            // Assign new token
            debounceToken = new CancellationTokenSource();

            // Debounce delay
            await Task.Delay(milliseconds, debounceToken.Token);

            // Throw if canceled
            debounceToken.Token.ThrowIfCancellationRequested();

            // Run function
            await func(debounceToken.Token);
        }
        catch (TaskCanceledException) { }
    }
}

Example call on a search function

<input type="text" @oninput=@(async (eventArgs) => await OnSearchInput(eventArgs)) />

@code {
    private readonly DebounceHelper debouncer = new DebounceHelper();

    private async Task OnSearchInput(ChangeEventArgs eventArgs)
    {
        await debouncer.DebounceAsync(async (cancellationToken) =>
        {
            // Search Code Here         
        });
    }
}

3
  • Is there a good reason you're throwing inside a try block if cancelled? Usually if(token.IsCancellationRequested) {return;} is better to avoid the exceptions as control flow antipattern. Commented Mar 31, 2022 at 19:18
  • @TheAtomicOption Not necessarily. I had not heard that was an anti-pattern. Just to confirm, you feel debounceToken.Token.ThrowIfCancellationRequested(); should be replaced with if (debounceToken.Token.IsCancellationRequested) { return; } Commented Mar 31, 2022 at 20:59
  • Yes you understood correctly. However on testing it myself, Task.Delay(milliseconds, debounceToken.Token) throws when cancellation is requested, so you'd also have to avoid passing the token and let the Delay task finish normally. I guess if it's designed to throw, then it's designed to throw. Commented Mar 31, 2022 at 22:20
1

Simply remember the latest 'hit:

DateTime latestHit = DatetIme.MinValue;

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
    if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
    {
        // ignore second hit, too fast
        return;
    }
    latestHit = DateTime.Now;
    // it was slow enough, do processing
    ...
}

This will allow a second event if there was enough time after the last event.

Please note: it is not possible (in a simple way) to handle the last event in a series of fast events, because you never know which one is the last...

...unless you are prepared to handle the last event of a burst which is a long time ago. Then you have to remember the last event and log it if the next event is slow enough:

DateTime latestHit = DatetIme.MinValue;
Machine historicEvent;

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");

    if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
    {
        // ignore second hit, too fast
        historicEvent = Machine; // or some property
        return;
    }
    latestHit = DateTime.Now;
    // it was slow enough, do processing
    ...
    // process historicEvent
    ...
    historicEvent = Machine; 
}
3
  • I want the opposite: I want to consider the last last one not the first.
    – Tobia
    Commented Feb 12, 2015 at 8:13
  • I maybe do not what is the last, but I can wait to do this event with a timer, and if nothing happends for 1-2s I do not abort the timer and let the callback do its job. Otherwise, I another event is fired, I have to reset the timer and start waiting again for 1-2s.
    – Tobia
    Commented Feb 12, 2015 at 8:17
  • Without a timer, this can only work if you get another event XYZ ms after the last Commented Feb 12, 2015 at 8:17
1

I did some more simple solution based on @Mike Ward answer:

public static class CustomTaskExtension
{
    #region fields

    private static int _last = 0;

    #endregion

    public static void Debounce(CancellationTokenSource throttleCts, double debounceTimeMs, Action action)
    {
        var current = Interlocked.Increment(ref _last);
        Task.Delay(TimeSpan.FromMilliseconds(debounceTimeMs), throttleCts.Token)
            .ContinueWith(task =>
            {
                if (current == _last) action();
                task.Dispose();
            });
    }
}

Example how to use it:

// security way to cancel the debounce process any time
CancellationTokenSource _throttleCts = new CancellationTokenSource();

public void MethodCalledManyTimes()
{
    // will wait 250ms after the last call
    CustomTaskExtension.Debounce(_throttleCts, 250, async () =>
    {
        Console.Write("Execute your code 250ms after the last call.");
    });
}
0

I came up with this in my class definition.

I wanted to run my action immediately if there hasn't been any action for the time period (3 seconds in the example).

If something has happened in the last three seconds, I want to send the last thing that happened within that time.

    private Task _debounceTask = Task.CompletedTask;
    private volatile Action _debounceAction;

    /// <summary>
    /// Debounces anything passed through this 
    /// function to happen at most every three seconds
    /// </summary>
    /// <param name="act">An action to run</param>
    private async void DebounceAction(Action act)
    {
        _debounceAction = act;
        await _debounceTask;

        if (_debounceAction == act)
        {
            _debounceTask = Task.Delay(3000);
            act();
        }
    }

So, if I have subdivide my clock into every quarter of a second

  TIME:  1e&a2e&a3e&a4e&a5e&a6e&a7e&a8e&a9e&a0e&a
  EVENT:  A         B    C   D  E              F  
OBSERVED: A           B           E            F

Note that no attempt is made to cancel the task early, so it's possible for actions to pile up for 3 seconds before eventually being available for garbage collection.

2
  • I tried this as I liked how clean the code was, but this results in performance issues when shutting down the app, any idea why that might be? Changed nothing else, other than adding this code. Commented Oct 23, 2019 at 13:41
  • If there's a delay at shutdown of the time requested, say 3 seconds, you could pass the same cancellation token to all of the calls to Task.Delay, and set the cancellation token when you shutdown. Note that cancelling will also cause an exception you'll have to deal with around Task.Delay Commented Oct 24, 2019 at 14:19
0

Figured out how to use System.Reactive NuGet package for doing a proper debouncing on a TextBox.

At the class level, we have our field

private IObservable<EventPattern<TextChangedEventArgs>> textChanged;

Then when we want to start listening to the event:

// Debouncing capability
textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
    Debug.WriteLine("bounce!");
});

Make sure you don't also wire your textbox up to an event handler. The Lambda above is the event handler.

0

I wrote an async debouncer that doesn't run async-in-sync.

public sealed class Debouncer : IDisposable {

  public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);

  private readonly TimeSpan _delay;
  private CancellationTokenSource? previousCancellationToken = null;

  public async Task Debounce(Action action) {
    _ = action ?? throw new ArgumentNullException(nameof(action));
    Cancel();
    previousCancellationToken = new CancellationTokenSource();
    try {
      await Task.Delay(_delay, previousCancellationToken.Token);
      await Task.Run(action, previousCancellationToken.Token);
    }
    catch (TaskCanceledException) { }    // can swallow exception as nothing more to do if task cancelled
  }

  public void Cancel() {
    if (previousCancellationToken != null) {
      previousCancellationToken.Cancel();
      previousCancellationToken.Dispose();
    }
  }

  public void Dispose() => Cancel();

}

I use it to debounce changes reported on file changes, see complete example here.

0

I was inspired by Mike's answer, but needed solution that worked without tasks, which simply swallows subsequent event invocations until debounce time-out runs out. Here's my solution:

public static Action<T> Debounce<T>(this Action<T> action, int milliseconds = 300)
{
    DateTime? runningCallTime = null;
    var locker = new object();

    return arg =>
    {
        lock (locker)
        {
            if (!runningCallTime.HasValue ||
                runningCallTime.Value.AddMilliseconds(milliseconds) <= DateTime.UtcNow)
            {
                runningCallTime = DateTime.UtcNow;
                action.Invoke(arg);
            }
        }
    };

}
0

Another implementation

public static class Debounce
{
    public static Action Action(Action action, TimeSpan time)
    {
        var timer = new Timer(_ => action(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);

        return () => timer.Change(time, Timeout.InfiniteTimeSpan);
    }
}
0

None of the above answers fully worked for me, so I've come up with the following implementation:

public class Debouncer
{
    private CancellationTokenSource _cancelTokenSource = null;

    public Task Debounce(Func<Task> method, int milliseconds = 250)
    {
        _cancelTokenSource?.Cancel();
        _cancelTokenSource?.Dispose();
        _cancelTokenSource = new CancellationTokenSource();

        try
        {
            return Task.Delay(milliseconds, _cancelTokenSource.Token)
                .ContinueWith(_ => method(), _cancelTokenSource.Token);
        }
        catch (TaskCanceledException exception) when (exception.CancellationToken == _cancelTokenSource.Token)
        {
        }
        
        return Task.CompletedTask;
    }
}

Usage:

var debouncer = new Debouncer();
await debouncer.Debounce(async () => await someAction());
0

I use this implementation for GUI/WinForms applications.

It's designed to keep a WeakReference to it's invocation target to avoid memory leaks. The try/catch is intentional ignoring the ObjectDisposedException since it's logical that the method invocation isn't relevant anymore.

public static class ThrottledActionFactory {
    public static readonly TimeSpan DEFAULT_THROTTLE_TIME = TimeSpan.FromMilliseconds(300);

    public static Action Create(this Action action, TimeSpan? time = null, TaskScheduler? scheduler = null) {
        var throttledWrapper = CreateThrottledAction(action.Target, action.Method, time, scheduler);
        return () => throttledWrapper(new object?[] { });
    }

    public static Action<T> Create<T>(this Action<T> action, TimeSpan? time = null, TaskScheduler? scheduler = null) {
        var throttledWrapper = CreateThrottledAction(action.Target, action.Method, time, scheduler);
        return arg => throttledWrapper(new object?[] {arg});
    }

    public static Action<T1, T2> Create<T1, T2>(this Action<T1, T2> action, TimeSpan? time = null, TaskScheduler? scheduler = null) {
        var throttledAction = CreateThrottledAction(action.Target, action.Method, time, scheduler);
        return (arg1, arg2) => throttledAction(new object?[] {arg1, arg2});
    }

    private static Action<object?[]> CreateThrottledAction(object target, MethodInfo method, TimeSpan? time, TaskScheduler? scheduler) {
        var targetReference = new WeakReference<object>(target);
        return CreateThrottledAction(targetReference, method, time ?? DEFAULT_THROTTLE_TIME, scheduler ?? TaskScheduler.Default);
    }

    private static Action<object?[]> CreateThrottledAction(WeakReference<object> targetReference, MethodInfo method, TimeSpan time, TaskScheduler scheduler) {
        CancellationTokenSource? cts = null;
        return args => {
            cts?.Cancel();
            cts = new();
            Task.Delay(time, cts.Token)
               .ContinueWith(delayTask => {
                    if (delayTask.IsCanceled || cts.IsCancellationRequested) {
                        return;
                    }

                    if (!targetReference.TryGetTarget(out var target)) {
                        return;
                    }

                    try {
                        method.Invoke(target, args);
                    } catch (ObjectDisposedException) {
                        // ignored
                    }
                }, scheduler);
        };
    }
}

Lastly it's possible to pass a TaskScheduler to the factory function in order to keep the execution on the main thread.

public class MyForm : Form {

    ...

    private readonly Action<string> LogConsole = ThrottledActionFactory.Create<string>(msg => {
        Console.WriteLine(msg);
    }, scheduler: TaskScheduler.FromCurrentSynchronizationContext());

    private void OnMouseMove(object sender, EventArgs args) {
        // executes on main thread because of TaskScheduler.FromCurrentSynchronizationContext()
        LogConsole("My Message"); 
    }
}
0

I use this to prevent debounce when the USB device is plugged in and unplugged. You can use this for your needs.

public class Debouncer : IDisposable
{
    readonly TimeSpan _delay;
    readonly Action _action;
    private CancellationTokenSource _lastToken;
    private static int _lastCount = 0;

    public Debouncer(TimeSpan delay, Action action)
    {
        if (delay.Milliseconds < 300)
        {
            delay = TimeSpan.FromMilliseconds(300); // minimum debounce delay
        }
        _delay = delay;
        _action = action;
    }

    public void Invoke()
    {
        try
        {
            if (_lastToken != null)
            {
                Cancel(_lastToken);
            }

            var currentCount = Interlocked.Increment(ref _lastCount);
            var cancelToken = _lastToken = new CancellationTokenSource();

            Task.Delay(_delay).ContinueWith(task =>
            {
                if (currentCount == _lastCount)
                {
                    _action();
                }
            }, cancelToken.Token);
        }
        catch (Exception ex)
        {
            Log.Exception(ex);
        }
    }
    public void Cancel(CancellationTokenSource token)
    {
        try
        {
            if (token != null)
            {
                token.Cancel();
                token.Dispose();
            }
        }
        catch (Exception ex)
        {
            Log.Exception(ex);
        }
    }
    public void Dispose()
    {
        try
        {
            Cancel(_lastToken);
        }
        catch (Exception ex)
        {
            Log.Exception(ex);
        }
    }
    ~Debouncer()
    {
        Dispose();
    }
}

How to use above debouncer?

protected override void WndProc(ref Message m)
{
    // detect when usb device (reader, printer) is plug or unplug
    try
    {
        base.WndProc(ref m);
        if (m.Msg == UsbNotify.WM_DEVICECHANGE)
        {
            switch ((int)m.WParam)
            {
                case UsbNotify.DBT_DEVICEREMOVECOMPLETE:
                    new Debouncer(TimeSpan.FromMilliseconds(300), UsbDetached).Invoke();
                    break;

                case UsbNotify.DBT_DEVICEARRIVAL:
                    new Debouncer(TimeSpan.FromMilliseconds(300), UsbAttached).Invoke();
                    break;
            }
        }
    }
    catch (Exception ex)
    {
        Log.Exception(ex);
    }
}
0

Takes in an aggregation function to generate an aggregated input and finally uses that aggregated input in the resulting task. Multiple classes can await the single task in order to have multiple threads waiting for a single operation's result.

public static class Debouncer
    {
        static object lockObject = new object();
        static ConcurrentDictionary<string, Tuple<Task, DateTime, object>> _taskTimeInputDictionary = new();
        public static async Task<T> Debounce<inputT, T>(string uniqueKey, int milliseconds, Func<inputT,inputT> inputAggregator, Func<inputT,T> action)
        {
            Task<T> task = null;
            lock (lockObject)
            {
                task = (Task<T>)(_taskTimeInputDictionary.AddOrUpdate(uniqueKey,
                    (key) => //key not found - create new
                    {
                        return new Tuple<Task, DateTime, object>(Task.Run(async () =>
                        {
                            while (true)
                            {
                                object input;
                                bool Run;
                                lock (lockObject) {
                                    var executeTime = _taskTimeInputDictionary[uniqueKey].Item2;
                                    input = _taskTimeInputDictionary[uniqueKey].Item3;
                                    Run = DateTime.Now > executeTime;
                                    if (Run)
                                    {
                                        _taskTimeInputDictionary.Remove(uniqueKey, out Tuple<Task, DateTime, object> _);
                                    }
                                }
                                if (Run) 
                                    return action((inputT) input);
                                else await Task.Yield();
                            }
                        }), DateTime.Now.AddMilliseconds(milliseconds), inputAggregator(default(inputT)));
                    },
                    (key, tt) =>
                    {
                        return new Tuple<Task, DateTime, object>(tt.Item1, DateTime.Now.AddMilliseconds(milliseconds), inputAggregator((inputT)tt.Item3));
                    }
                ).Item1);
            }
            return await task;
        }
    }

The locking object is only being used because for some reason the ConcurrentDictionary is not properly doing the job of preventing multiple adds for the same key at the same time. So by locking the object we remove any chance for issues related to the dictionary. An improvement to this could be to have multiple locking objects per unique key so as to not lock across uniqueKeys.

-1

I know I'm a couple hundred thousand minutes late to this party but I figured I'd add my 2 cents. I'm surprised no one has suggested this so I'm assuming there's something I don't know that might make it less than ideal so maybe I'll learn something new if this gets shot down. I often use a solution that uses the System.Threading.Timer's Change() method.

using System.Threading;

Timer delayedActionTimer;

public MyClass()
{
    // Setup our timer
    delayedActionTimer = new Timer(saveOrWhatever, // The method to call when triggered
                                   null, // State object (Not required)
                                   Timeout.Infinite, // Start disabled
                                   Timeout.Infinite); // Don't repeat the trigger
}

// A change was made that we want to save but not until a
// reasonable amount of time between changes has gone by
// so that we're not saving on every keystroke/trigger event.
public void TextChanged()
{
    delayedActionTimer.Change(3000, // Trigger this timers function in 3 seconds,
                                    // overwriting any existing countdown
                              Timeout.Infinite); // Don't repeat this trigger; Only fire once
}

// Timer requires the method take an Object which we've set to null since we don't
// need it for this example
private void saveOrWhatever(Object obj) 
{
    /*Do the thing*/
}
3
  • A timer is a bit overkill for what is needed, the other examples only execute code as and when required, where as this continues to execute code regardless of any events taking place. Commented Oct 23, 2019 at 13:42
  • This doesn't continuously execute code. When TextChanged is called, it schedules the saveOrWhatever method to be called 3 seconds later. If TextChanged is called again before saveOrWhatever is called, it resets the timer so it will only be called after 3 seconds has gone by since the last TextChanged call. The second parameter of Change on the timer object is the re-trigger rate. When set to infinite, it will not retrigger after the first code execution. Commented Oct 24, 2019 at 14:54
  • It continuously executes a timer. Commented Oct 24, 2019 at 14:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.