Created
June 5, 2022 21:01
-
-
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#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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