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.