C# event debounce

后端 未结 14 1026
南旧
南旧 2020-11-28 06:22

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

相关标签:
14条回答
  • 2020-11-28 06:41

    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);
    
    0 讨论(0)
  • 2020-11-28 06:43

    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*/
    }
    
    0 讨论(0)
  • 2020-11-28 06:46

    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);
    
    0 讨论(0)
  • 2020-11-28 06:46

    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         
            });
        }
    }
    
    
    0 讨论(0)
  • 2020-11-28 06:53

    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.

    0 讨论(0)
  • 2020-11-28 06:56

    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.

    0 讨论(0)
提交回复
热议问题