C# event debounce

后端 未结 14 1027
南旧
南旧 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:59

    I've used this to debounce events with some success:

    public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
    {
        var last = 0;
        return arg =>
        {
            var current = Interlocked.Increment(ref last);
            Task.Delay(milliseconds).ContinueWith(task =>
            {
                if (current == last) func(arg);
                task.Dispose();
            });
        };
    }
    

    Usage

    Action<int> a = (arg) =>
    {
        // This was successfully debounced...
        Console.WriteLine(arg);
    };
    var debouncedWrapper = a.Debounce<int>();
    
    while (true)
    {
        var rndVal = rnd.Next(400);
        Thread.Sleep(rndVal);
        debouncedWrapper(rndVal);
    }
    

    It may not be a robust as what's in RX but it's easy to understand and use.

    Followup 2020-02-03

    Revised @collie's solution using cancellation tokens as follows

    public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
    {
        CancellationTokenSource? cancelTokenSource = null;
    
        return arg =>
        {
            cancelTokenSource?.Cancel();
            cancelTokenSource = new CancellationTokenSource();
    
            Task.Delay(milliseconds, cancelTokenSource.Token)
                .ContinueWith(t =>
                {
                    if (t.IsCompletedSuccessfully)
                    {
                        func(arg);
                    }
                }, TaskScheduler.Default);
        };
    }
    

    Notes:

    • Calling Cancel is enough to dispose of the CTS
    • A successfully completed CTS is not canceled/disposed until the next call
    • As noted by @collie, tasks get disposed so no need to call Dispose on the task

    I've not worked with cancellation tokens before and may not be using them correctly.

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

    Recently I was doing some maintenance on an application that was targeting an older version of the .NET framework (v3.5).

    I couldn't use Reactive Extensions nor Task Parallel Library, but I needed a nice, clean, consistent way of debouncing events. Here's what I came up with:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    
    namespace MyApplication
    {
        public class Debouncer : IDisposable
        {
            readonly TimeSpan _ts;
            readonly Action _action;
            readonly HashSet<ManualResetEvent> _resets = new HashSet<ManualResetEvent>();
            readonly object _mutex = new object();
    
            public Debouncer(TimeSpan timespan, Action action)
            {
                _ts = timespan;
                _action = action;
            }
    
            public void Invoke()
            {
                var thisReset = new ManualResetEvent(false);
    
                lock (_mutex)
                {
                    while (_resets.Count > 0)
                    {
                        var otherReset = _resets.First();
                        _resets.Remove(otherReset);
                        otherReset.Set();
                    }
    
                    _resets.Add(thisReset);
                }
    
                ThreadPool.QueueUserWorkItem(_ =>
                {
                    try
                    {
                        if (!thisReset.WaitOne(_ts))
                        {
                            _action();
                        }
                    }
                    finally
                    {
                        lock (_mutex)
                        {
                            using (thisReset)
                                _resets.Remove(thisReset);
                        }
                    }
                });
            }
    
            public void Dispose()
            {
                lock (_mutex)
                {
                    while (_resets.Count > 0)
                    {
                        var reset = _resets.First();
                        _resets.Remove(reset);
                        reset.Set();
                    }
                }
            }
        }
    }
    

    Here's an example of using it in a windows form that has a search text box:

    public partial class Example : Form 
    {
        private readonly Debouncer _searchDebouncer;
    
        public Example()
        {
            InitializeComponent();
            _searchDebouncer = new Debouncer(TimeSpan.FromSeconds(.75), Search);
            txtSearchText.TextChanged += txtSearchText_TextChanged;
        }
    
        private void txtSearchText_TextChanged(object sender, EventArgs e)
        {
            _searchDebouncer.Invoke();
        }
    
        private void Search()
        {
            if (InvokeRequired)
            {
                Invoke((Action)Search);
                return;
            }
    
            if (!string.IsNullOrEmpty(txtSearchText.Text))
            {
                // Search here
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题