C# Event Debounce

C# event debounce

This isn't a trivial request to code from scratch as there are several nuances. A similar scenario is monitoring a FileSystemWatcher and waiting for things to quiet down after a big copy, before you try to open the modified files.

Reactive Extensions in .NET 4.5 were created to handle exactly these scenarios. You can use them easily to provide such functionality with methods like Throttle, Buffer, Window or Sample. You post the events to a Subject, apply one of the windowing functions to it, for example to get a notification only if there was no activity for X seconds or Y events, then subscribe to the notification.

Subject<MyEventData> _mySubject=new Subject<MyEventData>();
....
var eventSequenc=mySubject.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(events=>MySubscriptionMethod(events));

Throttle returns the last event in a sliding window, only if there were no other events in the window. Any event resets the window.

You can find a very good overview of the time-shifted functions here

When your code receives the event, you only need to post it to the Subject with OnNext:

_mySubject.OnNext(MyEventData);

If your hardware event surfaces as a typical .NET Event, you can bypass the Subject and manual posting with Observable.FromEventPattern, as shown here:

var mySequence = Observable.FromEventPattern<MyEventData>(
h => _myDevice.MyEvent += h,
h => _myDevice.MyEvent -= h);
_mySequence.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(events=>MySubscriptionMethod(events));

You can also create observables from Tasks, combine event sequences with LINQ operators to request eg: pairs of different hardware events with Zip, use another event source to bound Throttle/Buffer etc, add delays and a lot more.

Reactive Extensions is available as a NuGet package, so it's very easy to add them to your project.

Stephen Cleary's book "Concurrency in C# Cookbook" is a very good resource on Reactive Extensions among other things, and explains how you can use it and how it fits with the rest of the concurrent APIs in .NET like Tasks, Events etc.

Introduction to Rx is an excellent series of articles (that's where I copied the samples from), with several examples.

UPDATE

Using your specific example, you could do something like:

IObservable<MachineClass> _myObservable;

private MachineClass connect()
{

MachineClass rpc = new MachineClass();
_myObservable=Observable
.FromEventPattern<MachineClass>(
h=> rpc.RxVARxH += h,
h=> rpc.RxVARxH -= h)
.Throttle(TimeSpan.FromSeconds(1));
_myObservable.Subscribe(machine=>eventRxVARxH(machine));
return rpc;
}

This can be improved vastly of course - both the observable and the subscription need to be disposed at some point. This code assumes that you only control a single device. If you have many devices, you could create the observable inside the class so that each MachineClass exposes and disposes its own observable.

Debounce event/command in MVVM pattern

You could do this in a couple of ways.

  1. Reactive Extensions

The desired behavior can be achieved using the Throttle operator.

Observable
.FromEventPattern<EventArgs>(MapView, nameof(ViewpointChanged));
.Throttle(TimeSpan.FromMilliSeconds(300));
.Subscribe(eventPattern => vm.UpdateMapItems(eventPattern.Sender.VisibleArea.Extent));

When using FromEventPattern we're mapping events to instances of EventPattern, which includes the Sender (source) of the event.

I tested by subscribing to a UIElement's PointerMoved event. Which triggers HandleEvent multiple times if we keep moving. With Throttle, however, the event handler is executed only once. This is when the interval has passed after we stop moving.

MainPage.xaml

<Page
x:Class="..."
...
>
<Grid>
<Button x:Name="MyUIElement" Content="Throttle Surface"
Height="250" Width="250" HorizontalAlignment="Center"/>
</Grid>
</Page>

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();

Observable
.FromEventPattern<PointerRoutedEventArgs>(MyUIElement, nameof(UIElement.PointerMoved))
.Throttle(TimeSpan.FromMilliseconds(300))
.Subscribe(eventPattern => HandleEvent(eventPattern.Sender, eventPattern.EventArgs));
}

private void HandleEvent(object source, PointerRoutedEventArgs args)
{
Debug.WriteLine("Pointer Moved");
}
}

  1. Something custom

Our custom Throttle class keeps track of the last sender and args that have been processed. Processed as in "passed to Throttle for processing". Only when the timer elapses, and no other events have occurred, is the eventHandler (passed as a constructor argument) actually executed.

public class Throttle<TEventArgs>
{
private readonly DispatcherTimer _timer;
private object _lastSender;
private TEventArgs _lastEventArgs;

public Throttle(EventHandler<TEventArgs> eventHandler, TimeSpan interval)
{
_timer = new DispatcherTimer
{
Interval = interval
};
_timer.Tick += (s, e) =>
{
_timer.Stop();
eventHandler(_lastSender, _lastEventArgs);
};
}

public void ProcessEvent(object sender, TEventArgs args)
{
_timer.Stop();
_timer.Start();

_lastSender = sender;
_lastEventArgs = args;
}
}

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
private readonly Throttle<PointerRoutedEventArgs> _throttle;

public MainPage()
{
this.InitializeComponent();

var interval = TimeSpan.FromMilliseconds(300);
_throttle = new Throttle<PointerRoutedEventArgs>(HandleEvent, interval);
MyUIElement.PointerMoved += (sender, e) => _throttle.ProcessEvent(sender, e);
}

private void HandleEvent(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("Pointer Moved");
}
}

Update

I'm struggling to work out how everything fits together in an MVVM environment. The logic that needs to be triggered by the event is contained within the ViewModel but the View and ViewModel should be entirely separate.

There's a couple things I'd like to mention:

  • You're right about the need for separation of concerns, but a lot of devs are unclear of what that exactly entails. The view model should be completely unaware of who's listening, there's no doubt about that. But the view is dependent on the view model to get its data, so it's okay for the view to know about the view model. The problem is more about doing so in a loosely coupled way, ie. using bindings and contracts instead of directly accessing view model members.
  • That's why I'm not particularly fond of Caliburn's Actions. With cal:Message.Attach there's no contract (eg. ICommand) to decouple view syntax from the view model's. Of course, there are bindings in play, so you still get decoupled MVVM layers.

Long story short, there's a reason people choose ReactiveUI over Rx.NET for WPF development.
From the view's code behind (_.xaml.cs) it gives you access to:

  • The backing ViewModel
  • A binding system to keep it all loosely coupled

And, of course, ReactiveCommands, which would also come in handy in your use case.

Final thoughts, if your view has the same lifetime as your view model (ie. they get disposed together), you could be pragmatic about it and get the view model through the DataContext of your view.

Use an RX observable to create a debounce effect

You basically answered your question with this:

public MyClassThatDownloadsViaFtp(IObservable<FileToDownload> filesToDownloadViaFtp)
{
filesToDownloadViaFtp
.Select(DownloadFileViaFtp)
.Throttle(TimeSpan.FromMinutes(1))
.Subscribe(_ => DisconnectFtpClient());
}

If you don't have a convenient stream like filesToDownloadViaFtp then create one from either Observable.Create or Observable.FromEvent, or Observable.FromEventPattern, etc..

One quibble: Select is ideally run with no side-effects and DownloadFileViaFtp is very much a side-effect. Side-effects are best in a Subscribe call.

How can I debounce a C# Windows Forms ComboBox.PreviewKeyDown event?

The Control.PreviewKeyDown should only be used for testing for a particular key press and then to set the Control.IsInputKey to true if that is the case, otherwise you should use the Control.KeyDown event handler.

See Control.PreviewKeyDown Event

c# Throttle Example

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.

How to throttle the speed of an event without using Rx Framework

This works, if your event is of type EventHandler<EventArgs> for example. It creates a wrapper for your event handler that is throttled:

private EventHandler<EventArgs> CreateThrottledEventHandler(
EventHandler<EventArgs> handler,
TimeSpan throttle)
{
bool throttling = false;
return (s,e) =>
{
if(throttling) return;
handler(s,e);
throttling = true;
Task.Delay(throttle).ContinueWith(_ => throttling = false);
};
}

Attach like this:

this.SomeEvent += CreateThrottledEventHandler(
(s,e) => Console.WriteLine("I am throttled!"),
TimeSpan.FromSeconds(5));

Although, you should store the handler returned from CreateThrottledEventHandler if you need to unwire it with -= later.

A concise, simpler way to debounce the onclick event

There is an unexpectedly simple way to debounce an onclick event, requiring only two lines of javascript.

The approach sets the CSS pointer-events property of the EventTarget to none before resetting it to auto moments later:

const myFunction = (e) => {

e.target.style.setProperty('pointer-events', 'none');
setTimeout(() => {e.target.style.setProperty('pointer-events', 'auto')}, 800);

// REST OF FUNCTION HERE
}

Working Example:

const myElement = document.querySelector('.my-element');

const myFunction = (e) => {

e.target.classList.add('unclickable');
setTimeout(() => {e.target.classList.remove('unclickable')}, 2000);

console.log('myElement clicked');
}

myElement.addEventListener('click', myFunction, false);
.my-element {
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
font-family: sans-serif;
font-weight: 900;
color: rgb(255, 255, 255);
background-color: rgb(255, 0, 0);
transition: opacity 0.3s linear;
cursor: pointer;
}

.my-element.unclickable {
opacity: 0.5;
pointer-events: none;
}
<div class="my-element">Click me!</div>

Throttle an Event Handler

Using Rx, you want to use the Sample method or Throttle.

Something like this should work (untested):

Observable
.FromEventPattern<TextChangedEventArgs>(myUiElement, "MouseEnter")
.Sample(TimeSpan.FromSeconds(1))
.Subscribe(x => ... Do Stuff Here ...);

The difference between Sample and Throttle is that Sample will take a value every 1 second no matter when the last value was taken, whereas Throttle will take a value and then wait another 1 second before taking another.

It probably depends on what you are shooting for...



Related Topics



Leave a reply



Submit