Created
March 15, 2024 17:21
-
-
Save Gh61/6ad00d8b69e98c5d9f8e80567a825973 to your computer and use it in GitHub Desktop.
Class for creating debounced method in C# (includes support for Dispatcher)
This file contains 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
using System; | |
using System.Threading; | |
using System.Windows.Threading; | |
namespace Gh61 | |
{ | |
/// <summary> | |
/// Class for creating debounced variant of given Action, delay for given milliseconds. | |
/// Every <see cref="Invoke()"/> call will start the delay counting again. | |
/// </summary> | |
/// <remarks> | |
/// Not hitting the delay counter: | |
/// |--------------X |--------------X | |
/// ^ Invoke() ^ action ^ Invoke() ^ action | |
/// | |
/// Hitting the delay counter: | |
/// |-----------|--------------X | |
/// ^ Invoke() ^ Invoke() ^ action | |
/// | |
/// Cancelling | |
/// |-----------_ | |
/// ^ Invoke() ^ Cancel() | |
/// </remarks> | |
public class Debounced | |
{ | |
private Thread _thread; | |
private readonly Action _action; | |
private readonly int _waitMs; | |
private readonly int _tickMs; | |
private volatile int _remainingTicks; | |
private volatile bool _isCancelled; | |
/// <summary> | |
/// Will create debounced verion of given action. | |
/// </summary> | |
/// <param name="dispatcher">Dispatcher for running <see cref="action"/> within.</param> | |
/// <param name="action">Action to debounce.</param> | |
/// <param name="waitMs">How logn to wait before the action will be invoked.</param> | |
public Debounced(Dispatcher dispatcher, Action action, int waitMs) | |
: this(CreateDispatched(action, dispatcher), waitMs) | |
{ | |
} | |
/// <summary> | |
/// Will create debounced verion of given action. | |
/// </summary> | |
/// <param name="action">Action to debounce.</param> | |
/// <param name="waitMs">How logn to wait before the action will be invoked.</param> | |
public Debounced(Action action, int waitMs) | |
{ | |
if (waitMs < 5) | |
throw new ArgumentOutOfRangeException(nameof(waitMs), $@"The minimal value of {nameof(waitMs)} is 5."); | |
_action = action ?? throw new ArgumentNullException(nameof(action)); | |
_waitMs = waitMs; | |
_tickMs = waitMs / 5; | |
} | |
/// <summary> | |
/// Will start the waiting before action invoke. | |
/// </summary> | |
public Debounced Invoke() | |
{ | |
_remainingTicks = _waitMs / _tickMs; | |
_isCancelled = false; | |
if (_thread == null) | |
{ | |
_thread = new Thread(ThreadStart); | |
_thread.IsBackground = true; | |
_thread.Start(); | |
} | |
return this; | |
} | |
/// <summary> | |
/// Will cancel the pending waiting and stops the action invoke. | |
/// </summary> | |
public Debounced Cancel() | |
{ | |
_isCancelled = true; | |
return this; | |
} | |
private void ThreadStart() | |
{ | |
while (true) | |
{ | |
Thread.Sleep(_tickMs); | |
Interlocked.Decrement(ref _remainingTicks); | |
if (_isCancelled) | |
{ | |
break; | |
} | |
if (_remainingTicks <= 0 && _action != null) | |
{ | |
_action(); | |
break; | |
} | |
} | |
_thread = null; | |
} | |
private static Action CreateDispatched(Action action, Dispatcher dispatcher) | |
{ | |
if (action == null) | |
throw new ArgumentNullException(nameof(action)); | |
if (dispatcher == null) | |
throw new ArgumentNullException(nameof(dispatcher)); | |
return () => | |
{ | |
if (dispatcher.CheckAccess()) | |
{ | |
action(); | |
} | |
else | |
{ | |
dispatcher.Invoke(action); | |
} | |
}; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment