Reputation: 1532
I'm trying to learn how throttling and debouncing work as I now have a need for them in my application.
I have found this article here that explains it pretty well
From what I understand,
Throttling limits the number of times a function can be called in a determined amount of time. For example, only allow xFunction to fire once every 5 seconds.
Debouncing fires xFunction once a given amount of time has passed since it was last called. For example, if a user clicks the mouse 1000 times, xFunction will fire 5 seconds after the last time it was called.
In trying to better understand throttling and how the class provided from the article linked above, I have created a console application in which if a user presses any key, the console application will display that key.
I am now trying to throttle the number of times the key is displayed however my code doesn't seem to be working. Whenever I hit a key, the key i pressed is displayed however the hello()
never runs.
here is what I have:
class Program
{
static void Main(string[] args)
{
DebounceDispatcher debounceDispatcher = new DebounceDispatcher();
ConsoleKeyInfo keyinfo;
keyinfo = Console.ReadKey();
do
{
keyinfo = Console.ReadKey();
debounceDispatcher.Throttle(5, param => hello());
}
while (keyinfo.Key != ConsoleKey.X);
}
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
hello();
}
private static void hello()
{
Console.WriteLine("Hello World 5 seconds");
}
}
here is the DebouncingThrottling class which you can also find here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace SyncManager
{
public class DebounceDispatcher
{
private DispatcherTimer timer;
private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
/// <summary>
/// Debounce an event by resetting the event timeout every time the event is
/// fired. The behavior is that the Action passed is fired only after events
/// stop firing for the given timeout period.
///
/// Use Debounce when you want events to fire only after events stop firing
/// after the given interval timeout period.
///
/// Wrap the logic you would normally use in your event code into
/// the Action you pass to this method to debounce the event.
/// Example: https://gist.github.com/RickStrahl/0519b678f3294e27891f4d4f0608519a
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Debounce(int interval, Action<object> action,
object param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher disp = null)
{
// kill pending timer and pending ticks
timer?.Stop();
timer = null;
if (disp == null)
disp = Dispatcher.CurrentDispatcher;
// timer is recreated for each event and effectively
// resets the timeout. Action only fires after timeout has fully
// elapsed without other events firing in between
timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
{
if (timer == null)
return;
timer?.Stop();
timer = null;
action.Invoke(param);
}, disp);
timer.Start();
}
/// <summary>
/// This method throttles events by allowing only 1 event to fire for the given
/// timeout period. Only the last event fired is handled - all others are ignored.
/// Throttle will fire events every timeout ms even if additional events are pending.
///
/// Use Throttle where you need to ensure that events fire at given intervals.
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Throttle(int interval, Action<object> action,
object param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher disp = null)
{
// kill pending timer and pending ticks
timer?.Stop();
timer = null;
if (disp == null)
disp = Dispatcher.CurrentDispatcher;
var curTime = DateTime.UtcNow;
// if timeout is not up yet - adjust timeout to fire
// with potentially new Action parameters
if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
interval -= (int)curTime.Subtract(timerStarted).TotalMilliseconds;
timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
{
if (timer == null)
return;
timer?.Stop();
timer = null;
action.Invoke(param);
}, disp);
timer.Start();
timerStarted = curTime;
}
}
}
any and all help is appreciated.
other links I have checked to try to help me understand: timers dispatcher youtube video
Upvotes: 2
Views: 6689
Reputation: 21709
Something is wrong with the implementation of Throttle
.
I changed it to this
private CancellationTokenSource cts = new CancellationTokenSource();
private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
public void Throttle(int interval, Action<object> action, object param = null)
{
cts.Cancel();
cts = new CancellationTokenSource();
var curTime = DateTime.UtcNow;
if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
interval -= (int)curTime.Subtract(timerStarted).TotalMilliseconds;
Task.Run(async delegate
{
await Task.Delay(interval, cts.Token);
action.Invoke(param);
});
timerStarted = curTime;
}
and called it with a 5000ms delay (you were creating only a 5ms delay as I noted in the comments to your question.
debounceDispatcher.Throttle(5000, _ => hello());
Basically I'm just launching a task that sleeps, then invokes the action. While it's sleeping in Task.Delay
if the cancellation token is canceled, the task gets canceled, not running the action.
You may have to change Debounce
to use cancellable Task
as well. The Task
based implementation has the advantage of not needing the WindowsBase package referenced from the original implementation.
As far as your understanding goes, you've got it. It's just that the implementation appears to be flawed.
Upvotes: 2