Skip to content

Instantly share code, notes, and snippets.

@rent-a-developer
Created June 5, 2022 21:01
Show Gist options
  • Save rent-a-developer/d1e832d9b0272dd9678f5d5d54f1a44e to your computer and use it in GitHub Desktop.
Save rent-a-developer/d1e832d9b0272dd9678f5d5d54f1a44e to your computer and use it in GitHub Desktop.
An implementation of a debounce and a throttle logic in C#
/// <summary>
/// Provides extension methods for the <see cref="Action" /> type.
/// </summary>
public static class ActionExtensions
{
/// <summary>
/// Creates a debounced version of the given function <paramref name="action" />.
/// Use this method when you want a function to be executed after a certain amount of time has passed since it was called the last time.
/// </summary>
/// <param name="action">The function to debounce.</param>
/// <param name="delay">The debounce delay.</param>
/// <returns>A debounced version of the function <paramref name="action" />.</returns>
/// <remarks>
/// When the returned action is invoked the specified callback <paramref name="action" /> will not be executed immediately.
///
/// Instead a timer is started that waits until the specified amount of time (<paramref name="delay" />) has passed.
/// Once the timer has elapsed the callback <paramref name="action" /> is executed.
///
/// If the returned action is invoked again before the timer has elapsed all previous invocations to the returned action are ignored and the timer starts again.
/// </remarks>
public static Action Debounce(this Action action, TimeSpan delay)
{
CancellationTokenSource? cancellationTokenSource = null;
return () =>
{
cancellationTokenSource?.Cancel();
cancellationTokenSource = new();
Task
.Delay(delay, cancellationTokenSource.Token)
.ContinueWith(task =>
{
if (task.IsCompletedSuccessfully)
{
action();
}
}, TaskScheduler.Default);
};
}
/// <summary>
/// Creates a throttled version of the given function <paramref name="action" />.
/// Use this method when you want a function to be executed at most once in a specified period of time, even if it is called multiple times during that period.
/// </summary>
/// <param name="action">The function to throttle.</param>
/// <param name="interval">
/// The throttle interval.
/// The function <paramref name="action" /> is only executed once during this time period, no matter how often it is called during that period.
/// </param>
/// <param name="dispatcher">The dispatcher to use to schedule executions of the function.</param>
/// <param name="dispatcherPriority">The dispatcher priority at which to schedule the execution of the function.</param>
/// <returns>A throttled version of the function <paramref name="action" />.</returns>
/// <example>
/// <code>
/// Action updateAction = () => { ... };
/// var throttledUpdateAction = updateAction.Throttle(TimeSpan.FromSeconds(1));
///
/// while(true)
/// {
/// Thread.Sleep(TimeSpan.FromMilliseconds(100));
/// throttledUpdateAction(); // Even though the function is called every 100ms it will only execute once per second.
/// }
/// </code>
/// </example>
public static Action Throttle(this Action action, TimeSpan interval, Dispatcher? dispatcher = null, DispatcherPriority dispatcherPriority = DispatcherPriority.Background)
{
if (dispatcher == null)
{
dispatcher = Dispatcher.CurrentDispatcher;
}
DateTime? lastExecutionTime = null;
var timer = new DispatcherTimer(dispatcherPriority, dispatcher);
timer.Tick += (_, _) => Execute();
void Execute()
{
timer.Stop();
var now = DateTime.UtcNow;
// Action was never executed before: Execute it now.
if (lastExecutionTime == null)
{
dispatcher.Invoke(() =>
{
action();
lastExecutionTime = DateTime.UtcNow;
});
return;
}
// Action was executed before: Check how long ago the last execution was.
var timePassedSinceLastExecution = now.Subtract(lastExecutionTime.Value);
// Enough time has passed since the last execution: Execute it now.
if (timePassedSinceLastExecution >= interval)
{
dispatcher.Invoke(() =>
{
action();
lastExecutionTime = DateTime.UtcNow;
});
return;
}
// Not enough time has passed since the last execution: Schedule the execution.
var executeIn = interval - timePassedSinceLastExecution;
timer.Interval = executeIn;
timer.Start();
}
return () => Execute();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment