C# event debounce

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.

Leave a Comment