Last active
August 20, 2024 10:15
-
-
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 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
using System; | |
using System.Threading; | |
using System.Windows.Threading; | |
using System.Windows.Forms; | |
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 long 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 version of given action. | |
/// </summary> | |
/// <param name="owner">Owner control for running <see cref="action"/> within.</param> | |
/// <param name="action">Action to debounce.</param> | |
/// <param name="waitMs">How long to wait before the action will be invoked.</param> | |
public Debounced(Control owner, Action action, int waitMs) | |
: this(CreateInvoked(action, owner), waitMs) | |
{ | |
} | |
/// <summary> | |
/// Will create debounced version of given action. | |
/// </summary> | |
/// <param name="action">Action to debounce.</param> | |
/// <param name="waitMs">How long 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); | |
} | |
}; | |
} | |
private static Action CreateInvoked(Action action, Control owner) | |
{ | |
if (action == null) | |
throw new ArgumentNullException(nameof(action)); | |
if (owner == null) | |
throw new ArgumentNullException(nameof(owner)); | |
return () => | |
{ | |
if (owner.InvokeRequired) | |
{ | |
action(); | |
} | |
else | |
{ | |
owner.Invoke(action); | |
} | |
}; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added methods for WinForms (Owner control)