I\'m listening to a hardware event message, but I need to debounce it to avoid too many queries.
This is an hardware event that sends the machine status and I have t
I needed something like this but in a web-application, so I can't store the Action
in a variable, it will be lost between http requests.
Based on other answers and @Collie idea I created a class that looks at a unique string key for throttling.
public static class Debouncer
{
static ConcurrentDictionary<string, CancellationTokenSource> _tokens = new ConcurrentDictionary<string, CancellationTokenSource>();
public static void Debounce(string uniqueKey, Action action, int seconds)
{
var token = _tokens.AddOrUpdate(uniqueKey,
(key) => //key not found - create new
{
return new CancellationTokenSource();
},
(key, existingToken) => //key found - cancel task and recreate
{
existingToken.Cancel(); //cancel previous
return new CancellationTokenSource();
}
);
Task.Delay(seconds * 1000, token.Token).ContinueWith(task =>
{
if (!task.IsCanceled)
{
action();
_tokens.TryRemove(uniqueKey, out _);
}
}, token.Token);
}
}
Usage:
//throttle for 5 secs if it's already been called with this KEY
Debouncer.Debounce("Some-Unique-ID", () => SendEmails(), 5);
As a side bonus, because it's based on a string key, you can use inline lambda's
Debouncer.Debounce("Some-Unique-ID", () =>
{
//do some work here
}, 5);
I know I'm a couple hundred thousand minutes late to this party but I figured I'd add my 2 cents. I'm surprised no one has suggested this so I'm assuming there's something I don't know that might make it less than ideal so maybe I'll learn something new if this gets shot down.
I often use a solution that uses the System.Threading.Timer
's Change()
method.
using System.Threading;
Timer delayedActionTimer;
public MyClass()
{
// Setup our timer
delayedActionTimer = new Timer(saveOrWhatever, // The method to call when triggered
null, // State object (Not required)
Timeout.Infinite, // Start disabled
Timeout.Infinite); // Don't repeat the trigger
}
// A change was made that we want to save but not until a
// reasonable amount of time between changes has gone by
// so that we're not saving on every keystroke/trigger event.
public void TextChanged()
{
delayedActionTimer.Change(3000, // Trigger this timers function in 3 seconds,
// overwriting any existing countdown
Timeout.Infinite); // Don't repeat this trigger; Only fire once
}
// Timer requires the method take an Object which we've set to null since we don't
// need it for this example
private void saveOrWhatever(Object obj)
{
/*Do the thing*/
}
This is inspired by Nieminen's Task.Delay-based Debouncer class. Simplified, some minor corrections, and should clean up after itself better.
class Debouncer: IDisposable
{
private CancellationTokenSource lastCToken;
private int milliseconds;
public Debouncer(int milliseconds = 300)
{
this.milliseconds = milliseconds;
}
public void Debounce(Action action)
{
Cancel(lastCToken);
var tokenSrc = lastCToken = new CancellationTokenSource();
Task.Delay(milliseconds).ContinueWith(task =>
{
action();
},
tokenSrc.Token
);
}
public void Cancel(CancellationTokenSource source)
{
if (source != null)
{
source.Cancel();
source.Dispose();
}
}
public void Dispose()
{
Cancel(lastCToken);
}
~Debouncer()
{
Dispose();
}
}
Usage
private Debouncer debouncer = new Debouncer(500); //1/2 a second
...
debouncer.Debounce(SomeAction);
I needed a Debounce method for Blazor and kept coming back to this page so I wanted to share my solution in case it helps others.
public class DebounceHelper
{
private CancellationTokenSource debounceToken = null;
public async Task DebounceAsync(Func<CancellationToken, Task> func, int milliseconds = 1000)
{
try
{
// Cancel previous task
if (debounceToken != null) { debounceToken.Cancel(); }
// Assign new token
debounceToken = new CancellationTokenSource();
// Debounce delay
await Task.Delay(milliseconds, debounceToken.Token);
// Throw if canceled
debounceToken.Token.ThrowIfCancellationRequested();
// Run function
await func(debounceToken.Token);
}
catch (TaskCanceledException) { }
}
}
Example call on a search function
<input type="text" @oninput=@(async (eventArgs) => await OnSearchInput(eventArgs)) />
@code {
private readonly DebounceHelper debouncer = new DebounceHelper();
private async Task OnSearchInput(ChangeEventArgs eventArgs)
{
await debouncer.DebounceAsync(async (cancellationToken) =>
{
// Search Code Here
});
}
}
I came up with this in my class definition.
I wanted to run my action immediately if there hasn't been any action for the time period (3 seconds in the example).
If something has happened in the last three seconds, I want to send the last thing that happened within that time.
private Task _debounceTask = Task.CompletedTask;
private volatile Action _debounceAction;
/// <summary>
/// Debounces anything passed through this
/// function to happen at most every three seconds
/// </summary>
/// <param name="act">An action to run</param>
private async void DebounceAction(Action act)
{
_debounceAction = act;
await _debounceTask;
if (_debounceAction == act)
{
_debounceTask = Task.Delay(3000);
act();
}
}
So, if I have subdivide my clock into every quarter of a second
TIME: 1e&a2e&a3e&a4e&a5e&a6e&a7e&a8e&a9e&a0e&a
EVENT: A B C D E F
OBSERVED: A B E F
Note that no attempt is made to cancel the task early, so it's possible for actions to pile up for 3 seconds before eventually being available for garbage collection.
Figured out how to use System.Reactive NuGet package for doing a proper debouncing on a TextBox.
At the class level, we have our field
private IObservable<EventPattern<TextChangedEventArgs>> textChanged;
Then when we want to start listening to the event:
// Debouncing capability
textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
Debug.WriteLine("bounce!");
});
Make sure you don't also wire your textbox up to an event handler. The Lambda above is the event handler.