Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Debouncing and Throttling Dispatcher Events


:P
On this page:

One thing I need quite frequently in Windows UI applications, is some way to manage events that are firing frequently, limiting the input based on a timeout. Rather than handling each and every event that fires I only want to handle one in a given timeout period or after a certain amount of idle time has passed.

These operations are called debouncing - where events are meant to be run out after an idle timeout has elapsed, or throttling - where events only fire in a specified interval. In both cases only the last event is fired.

Debouncing?

Yeah I know - it's a terrible term that means practically nothing to most people. It's derived from an old computer hardware term. Specifically it has its origins computer IO switches that needed to be debounced in order to not produce signal overlap.

The best definition and differentiation I found is in this excellent CSS-Tricks Blog Post:

Debouncing

Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called".

Throttling

Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds".

In other word: Throttling fires events in specific timeout intervals, while debouncing only fires one event - the last event - each time the event stream times out.

Debouncing and Throttling Use Cases

Debouncing and Throttling is useful for just about any event that fires more frequently than what you actually need to or want to handle. Lots of UI events fire in rapid succession often producing thousands of events a second, and if you handle some of these events with any sort of expensive logic it's easy to bring an application's UI to a crawl. Debouncing or Throttling can help fire these events less frequently and still provide for handling the logic of the core event to process.

Here are a couple of practical examples that I often use Debouncing or Throttling for and I'll provide concrete examples at the end of this post:

  • Search Box Input
    Debounce text input so a search operation doesn't fire while the user is still typing, but rather wait until after the user completes typing for a brief moment. This prevents me from firing filtering logic to apply the search filter - which can be slow - after every keystroke.

  • Displaying Messages that Timeout
    I often use Statusbar messages to display progress or status information to users and I want to have those messages time out with calls like ShowStatus("Weblog post uploaded...",6000) where the message goes away after 6 seconds and reverts to a default message. If you have multiple messages fired though within those 6 seconds the timeout fires in the middle of a still pending message. Debouncing ensures that only the last message timeout is applied and so the last message displayed is the one that eventually times out and goes away, while all the previous timeouts are ignored.

  • Resize Operations
    Resizing of Windows or Panels in an application can fire a crazy amount of events and if your resize logic affects a large number of layout items it can easily cause some annoying UI stutter. Throttling with very short intervals can help break down events into few more manageable calls that allow updating the UI in intervals you specify.

Debounce in .NET

There's no native functionality to provide debouncing or throttling in .NET.

There's support for a Throttle function in in the Reactive Extensions, but it looks like it actually implements Debounce behavior.

var input = searchTextChanged
    .Throttle(TimeSpan.FromMilliseconds(400))
    .Select(args => SearchText);

Personally I'm not using RX much, and I certainly won't add it to an app just for this feature. It also looks like Throttle actually implements Debounce (go figure) rather than Throttling. If you are already using RX by all means apply RX operators to provide Debounce/Throttling behavior.

Creating a DebounceDispatcher

Up until recently I've dealt with these throttling/debouncing scenarios primarily with custom logic. I'd add a timer, start and stop the timer. It's not overly complicated to do so, but it's a pain to add the properties and logic to each and every instance where this code is required.

In a recent conversation with Markus Egger, he pointed out that he uses the DispatcherTimer for these sort of things, which in turn led me to wrap up some of the logic we discussed in an easy to reuse class.

The following DebounceDispatcher provides both Debounce() and Throttle() methods.

/// <summary>
/// Provides Debounce() and Throttle() methods.
/// Use these methods to ensure that events aren't handled too frequently.
/// 
/// Throttle() ensures that events are throttled by the interval specified.
/// Only the last event in the interval sequence of events fires.
/// 
/// Debounce() fires an event only after the specified interval has passed
/// in which no other pending event has fired. Only the last event in the
/// sequence is fired.
/// </summary>
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;            
    }
}

Example Usage of Debounce

Let's look at a couple of Debounce use cases.

Search Text Box Filter

In many applications where there's list data I like to be able to filter the list data with a search box. For example, in the application in the Figure below I filter the topics of a documentation project:

This is a WPF application, so there are a couple of ways that the Debounce operation could be handled.

  • Hooking up an Event Handler
  • Hooking up to OnPropertyChanged() in the model
Event Handler

The most common way you're likely to use Debounce operations is by hooking an event handler using code like this:

private DebounceDispatcher debounceTimer = new DebounceDispatcher();
       
private void TextSearchText_KeyUp(object sender, KeyEventArgs e)
{
    debounceTimer.Debounce(500, (p) =>
    {                
        Model.AppModel.Window.ShowStatus("Searching topics...");
        Model.TopicsFilter = TextSearchText.Text;
        Model.AppModel.Window.ShowStatus();
    });        
}

This works, as long as the search value you are trapping is not bound to a model. So rather than using a model bound value, in the code above I explicitly use the KeyUp event to trap keystrokes and then debounce those keystrokes.

With Model Binding

If I'm using model binding like so:

 <TextBox Name="TextSearchText" 
          Text="{Binding TopicsFilter,UpdateSourceTrigger=PropertyChanged}" />

I can also trap the change binding in the model like this:

public string TopicsFilter
{
    get { return _topicsFilter; }
    set
    {
        if (value == _topicsFilter) return;
        _topicsFilter = value;
        OnPropertyChanged();

        // debounce the tree filter change notifications
        debounceTopicsFilter.Debounce(500, e => OnPropertyChanged(nameof(FilteredTopicTree)));
    }
}
private string _topicsFilter;
private readonly DebounceDispatcher debounceTopicsFilter = new DebounceDispatcher();

Notice the debouncing of the OnPropertyChanged("FilteredTopicTree") call, which is what eventually triggers the topic tree to reload the list with the new filter and then re-display the tree. This works great, although it's a bit unorthodox because it mixes some of the UI behavior into the model. However, in my case this is easily the best solution as it traps all cases where the search text is updated.

In this app the topic tree can be thousands of items and the re-render can take a few seconds. Unfortunately the overhead isn't the list retrieval which is pretty quick (half second for a few thousand records), but the actual data binding of the list is very slow (even with a virtualized tree).

Without debouncing the search functionality with a project with more than few hundred topics would be unusable. With debouncing - even with a few thousand topics the search functionality works well.

WPF and the Delay Binding Property

There's actually an easier way to do UI debouncing of binding events by using the WPF Delay binding property:

Text="{Binding TopicsFilter,UpdateSourceTrigger=PropertyChanged,Delay=500}"

which debounces the key input.

Fading Status Bar

Another example I use in most UI apps is a resetting status bar, where I can display a StatusMessage() with a timeout:

ShowStatus("Weblog entry posted to {blogName}",8000);

After 8 seconds the status resets to the default Ready message. Without debouncing the issue is if I have multiple messages that have timeouts, the first one to complete will reset the message even if subsequent messages are still pending and shouldn't reset yet. By debouncing I can insure that only the last ShowStatus() call resets the message.

The implementation of this method with debouncing is quite simple and looks like this:

 private DebounceDispatcher statusDebounce = new DebounceDispatcher();

public void ShowStatus(string message = null, int milliSeconds = 0)
{
    if (message == null)
    {
        message = "Ready";
        SetStatusIcon();
    }

    StatusText.Text = message;

    if (milliSeconds > 0)
    {
        statusDebounce.Debounce(milliSeconds, (win) =>
        {
            var window = win as MainWindow;
            window?.ShowStatus(null, 0);
        }, this);
    }
    WindowUtilities.DoEvents();
}

Throttling Window Resizing

In Markdown Monster the Window Resize operation causes various child panels to update themselves by resizing and re-organizing themselves. This resize can affect a lot of different controls and if left without any throttling, the display can get quite flickery.

By throttling I can delay the resize events a bit and fire those resizing events a lot less frequently drastically reducing the resize flicker.

private DebounceDispatcher resizeThrottle = new DebounceDispatcher();
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
    resizeThrottle.Throttle(5, parm => PanelRefresh());
}

Now the PanelRefresh fires only once every 5ms instead of tens of times per ms and the resizing operations are much smoother as a result. Yay!

Summary

Debounce and Throttle are pretty useful. Once I created these I've sprinkled this code into various areas of applications where event management required some scaling back. The use cases I described above took quite a bit more code before and these helpers made this code much simpler. While looking around for implementations I didn't find much except a number of Reactive Extensions examples, which frankly looked more cryptic than my original implementations.

Hopefully some of you may find this as useful as it's been for me.

Resources

this post created with Markdown Monster
Posted in WPF  C#  

The Voices of Reason


 

Benny
July 03, 2017

# re: Debouncing and Throttling Dispatcher Events

Hi Rick,

interesting way to set the limitation in the viewmodel without using RX extensions. The same functionality could be accomplished in the view, using the delay keyword in a binding. In most cases I would set the limitation in the view because the view requires the limitation. Thanks for sharing.


Rick Strahl
July 03, 2017

# re: Debouncing and Throttling Dispatcher Events

@Benny - thanks for pointing out the Delay Binding Property. I didn't know and that actually works for the Search... use case without using these helpers.

Text="{Binding TopicsFilter,UpdateSourceTrigger=PropertyChanged,Delay=500}"

Added a note into the document and a link to an article that explains. Thanks!


Muhammad Rehan Saeed
July 04, 2017

# re: Debouncing and Throttling Dispatcher Events

I highly recommend Rx for desktop apps, I used it a fair bit when I was more of a desktop developer. Where you should or shouldn't use Rx is a complicated question. I've seen a lot of MVVM frameworks go all out and use it for everything. I wrote a whole series of blog posts covering this topic. The TLDR is to use Rx to replace or wrap C# events and not in place of async/await as there is some overlap.


Khanh Vu
July 05, 2017

# re: Debouncing and Throttling Dispatcher Events

Hi Rick,

I haven't tried out your implementation yet. But I feel a bit confused with interval in Throtte method. I understand that the interval should be adjusted like:

// 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;

It's that 'interval' should be subtracted by the time period passed, isn't it?

Btw, thanks for your work and sharing.

Best regards.


Rick Strahl
July 05, 2017

# re: Debouncing and Throttling Dispatcher Events

Yup you're right. Not sure what happened to the missing - 😃.


Stephen Cleary
July 10, 2017

# re: Debouncing and Throttling Dispatcher Events

Rx is particularly useful for time-related algorithms; they handle a lot of corner cases for you. There's a very interesting "rethinking UI" project based on Rx called ReactiveUI (https://reactiveui.net/) which I sadly have still not found much time to play with yet.

Rx does have some unfortunate naming (largely due to its history as "LINQ over Events", and LINQ's choice of SQL terminology rather than map/reduce or functional terminology). Thus, Rx.NET's "Throttle" is in fact "Debounce" (http://reactivex.io/documentation/operators/debounce.html), and has another operator "Sample" (http://reactivex.io/documentation/operators/sample.html) that I think is what you're looking for with "Throttle".

IntroToRx is the best resource IMO for learning Rx. I use it all the time: http://www.introtorx.com/Content/v1.0.10621.0/13_TimeShiftedSequences.html#Sample


Terry
August 29, 2018

# re: Debouncing and Throttling Dispatcher Events

I'm not sure that Throttle() is working properly. Unless my understanding of how it's supposed to work is incorrect. timerStarted is reset on every call. So, effectively, it currently (incorrectly) runs like this:

// assuming it's called once every 100ms
[0ms]	.Throttle(1000, ...) { timer = new timer(interval = 1000), timerStarted = now}
[100ms]	.Throttle(1000, ...) { timer = new timer(interval = 900), timerStarted = now}
[200ms]	.Throttle(1000, ...) { timer = new timer(interval = 900), timerStarted = now}
[300ms]	.Throttle(1000, ...) { timer = new timer(interval = 900), timerStarted = now}
...
// So the action will only be invoked 900ms after the last attempt, 
// instead of once every 1000ms. So, it's just Debounce, essentially.

Here's my modified implementation, which I believe is correct:

// ...

// renamed timerStarted to timerReset

// ...

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;
        else
            timerReset = curTime;

        timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
        {
            if (timer == null)
                return;

            timer?.Stop();
            timer = null;
            action.Invoke(param);
            timerReset = curTime;
        }, disp);

        timer.Start();            
    }

now, it runs like this:

[0ms]	.Throttle(1000, ...) { timer = new timer(interval = 1000), timerStarted = now}
[100ms]	.Throttle(1000, ...) { timer = new timer(interval = 900), timerStarted = now}
[200ms]	.Throttle(1000, ...) { timer = new timer(interval = 800), timerStarted = now}
[300ms]	.Throttle(1000, ...) { timer = new timer(interval = 700), timerStarted = now}

George Gomez
April 22, 2019

# re: Debouncing and Throttling Dispatcher Events

Rick,

Very nice debouncer and throttler. I was wondering if you think that we need to implement IDisposable. Will the DispatcherTimer leak memory? Thank you!

George


Max
May 05, 2019

# re: Debouncing and Throttling Dispatcher Events

Hi Rick

First off, big fan of your blog, been following your asp.net comments many times 😃

I have to say this is a good classic debounce approach - one we used to take before we started using Angular and RxJs several years back. Today, we like to use Rx in C# too, because our programming team is very familiar with the Rx concepts, it's also surprisingly small in implementation.

I just wanted to share with you and example of keystroke debounce on a classic key up from a Windows Forms app (just as easy in WPF to do too). but wanted to illustrate 😃- it relies on outputting the event values to a behavior subject, that passes them to an observable that we then debounce.

Add this above the constructor of your form (or user control):

private BehaviorSubject<KeyEventArgs> _oFilterKeysBS = new BehaviorSubject<KeyEventArgs>(null); // will fire a null initially.

Add this in the constructor of your form (or user control):

// get the filter keys as observable and debounce.
_oFilterKeysBS.AsObservable().Throttle(TimeSpan.FromMilliseconds(500)).Subscribe(
	(e) => {
		if (e == null) return; // initialization (ignore)
		this.BeginInvoke((Action)(() =>
		{
			// execute code on the UI thread. Classic Windows Forms.
			// ex: use the filter text to filter the data
		}));
	},
	(ex) => { Console.WriteLine("Got exception: "+ex.Message); } , // optional
	() => { Console.WriteLine("Completed"); } // optional
);

And then feed the behavior subject from your classic .net event handler, there are other methods, but I find them too verbose:

private void tb_Filter_KeyUp(object sender, KeyEventArgs e)
{
	// output the key event to the Behavior Subject, will which fire the observable.
	_oFilterKeysBS.OnNext(e);
}

Then maybe I'll see you on the water some time in Hood River. But I tend to stay at the coast.

Cheers


Heiko Guckes
August 15, 2019

# re: Debouncing and Throttling Dispatcher Events

Hi Rick,

thank you for this post. I have to agree with Terry in that the Throttle function does not behave as described. Even with his fix, if the function is called very often in a very short timeframe or bad timing, no events at all will make it through because the timer will be killed by the next call.


Mehdi
October 31, 2019

# re: Debouncing and Throttling Dispatcher Events

Hi,

Thank you for sharing this.

Seems like there is no DispatcherTimer in .NetCore, is there any alternative?


Tore Aurstad
February 21, 2020

# re: Debouncing and Throttling Dispatcher Events

I wrote an article in 2015 about something I called 'Switched based delay with dispatcher' which I see is actually similar to debounce method here. The way I solved it supports a set of actions which you compartmentalize using global unique identifiers. Most major frameworks today include a debounce method, and web developers and people using npm take them for granted. Anyways, great article, I will look into testing out this code in my WPF MVVM application.

http://toreaurstad.blogspot.com/2015/10/switchbased-delay-with-dispatcher-in-wpf.html


Michael Janulaitis
September 05, 2020

# re: Debouncing and Throttling Dispatcher Events

Rick thanks for the article. I am using this to throttle a real-time log viewer and wanted my screen to immediately scroll if more than the interval has passed. In this way my users get better response when the log is slowly updating but the throttle is adhered to when the log is under load.

        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;

            var difference = (int)curTime.Subtract(timerStarted).TotalMilliseconds;

            // if timeout is not up yet - adjust timeout to fire 
            // with potentially new Action parameters           
            if (difference < interval)
                interval -= difference;
            else // fire immediately
                interval = 0;

            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;
        }

Andrei
May 11, 2022

# re: Debouncing and Throttling Dispatcher Events

Hi Rick, What is the license for the above DebounceDispatcher code? Thanks in advance!


Rick Strahl
May 11, 2022

# re: Debouncing and Throttling Dispatcher Events

If it's on this blog it's free to use.

I'm not going to put a bloody license on every bit of code I post.


Rick Wolff
August 02, 2023

# re: Debouncing and Throttling Dispatcher Events

Hey, Rick. It's Rick here too. 😃

Thanks for the code, I've been using it for a while. But now I'm writing a .NET 7 project and System.Windows.Threading is not available. Do you know if someone wrote a version of this to .NET (Core)?

Thank you!


Rick Strahl
August 02, 2023

# re: Debouncing and Throttling Dispatcher Events

You need to add <UseWpf>true</UseWpf> and target net70-windows I believe since these are features out of the WPF namespaces.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024