Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Debouncing and Throttling Dispatcher Events


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

 

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