Nano Hash - криптовалюты, майнинг, программирование

Обнаружение дребезга событий C#

Я прослушиваю сообщение об аппаратном событии, но мне нужно устранить его, чтобы избежать слишком большого количества запросов.

Это аппаратное событие, которое отправляет состояние машины, и я должен хранить его в базе данных для статистических целей, и иногда бывает, что его состояние очень часто меняется (мерцает?). В этом случае я хотел бы сохранить только «стабильный» статус, и я хочу реализовать его, просто подождав 1-2 секунды перед сохранением статуса в базе данных.

Это мой код:

private MachineClass connect()
{
    try
    {
        MachineClass rpc = new MachineClass();
        rpc.RxVARxH += eventRxVARxH;
        return rpc;
    }
    catch (Exception e1)
    {
        log.Error(e1.Message);
        return null;
    }
}

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
}

Я называю это поведение «debounce»: подождите несколько раз, чтобы действительно выполнить свою работу: если одно и то же событие запускается снова в течение времени debounce, я должен отклонить первый запрос и начать ждать время debounce, чтобы завершить второе событие.

Что является лучшим выбором для управления им? Просто одноразовый таймер?

Чтобы объяснить функцию «debounce», см. эту реализацию javascript для ключевых событий: http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/

12.02.2015

  • Используйте секундомер, чтобы измерить прошедшее время. 12.02.2015
  • Я должен отклонить первый запрос - это звучит очень проблематично. Есть ли конкретная причина, по которой первый запрос не может быть разрешен, а последующий игнорируется? 12.02.2015
  • Это аппаратное событие, которое отправляет статус машины, и я должен хранить его в базе данных для статистических целей, иногда бывает, что его статус очень часто меняется (мерцает?), в этом случае я хотел бы сохранить только стабильный статус, и я хотите реализовать его, просто подождав 1-2 секунды перед сохранением статуса в базе данных. Может быть, я могу сделать это с таймером? Я жду 1-2 секунды, чтобы запустить запрос, сбрасывающий его в случае слишком близкого изменения статуса. 12.02.2015
  • @Damien_The_Unbeliever это не странная просьба и не тривиальная. Это не может быть обработано с помощью простого таймера. Однако реактивные расширения охватывают этот сценарий. 12.02.2015
  • Очень похожая проблема — ожидание, пока FileSystemWatcher перестанет сообщать об изменениях, например. при копировании большого файла. Вы получаете много событий изменения, но действительно хотите немного подождать после события last, прежде чем пытаться получить доступ к измененному файлу. 12.02.2015
  • Проблема с попыткой сохранить последнее, а не первое, заключается в том, что вы почти всегда заканчиваете гонкой между потоками (или потоком и таймером). 12.02.2015

Ответы:


1

Это не тривиальная просьба писать код с нуля, так как есть несколько нюансов. Аналогичный сценарий отслеживает FileSystemWatcher и ждет, пока все успокоится после большой копии, прежде чем пытаться открыть измененные файлы.

Реактивные расширения в .NET 4.5 были созданы именно для таких сценариев. Вы можете легко использовать их для обеспечения такой функциональности с помощью таких методов, как Дроссель, Буфер, Окно или пример . Вы публикуете события в Subject , применить к нему одну из оконных функций, например, чтобы получать уведомление, только если не было активности в течение X секунд или Y событий, затем подпишитесь на уведомление.

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

Throttle возвращает последнее событие в скользящем окне, только если в окне не было других событий. Любое событие сбрасывает окно.

Очень хороший обзор функций со сдвигом во времени можно найти здесь

Когда ваш код получает событие, вам нужно только опубликовать его в теме с помощью OnNext:

_mySubject.OnNext(MyEventData);

Если ваше аппаратное событие отображается как типичное событие .NET, вы можете обойти тему и ручную публикацию с помощью Observable.FromEventPattern, как показано здесь:

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

Вы также можете создавать наблюдаемые из задач, комбинировать последовательности событий с операторами LINQ для запроса, например: пары различных аппаратных событий с Zip, использовать другой источник событий для привязки Throttle/Buffer и т. д., добавлять задержки и многое другое.

Реактивные расширения доступны в виде пакета NuGet, поэтому их очень легко добавить в ваш проект.

Книга Стивена Клири Параллелизм в C# Cookbook очень хороша. ресурс по реактивным расширениям, среди прочего, и объясняет, как вы можете его использовать и как он сочетается с остальными параллельными API в .NET, такими как задачи, события и т. д.

Введение в Rx – это отличная серия статей ( оттуда я скопировал образцы), с несколькими примерами.

ОБНОВЛЕНИЕ

Используя ваш конкретный пример, вы можете сделать что-то вроде:

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

Конечно, это можно значительно улучшить - и наблюдаемое, и подписка должны быть удалены в какой-то момент. Этот код предполагает, что вы управляете только одним устройством. Если у вас много устройств, вы можете создать наблюдаемое внутри класса, чтобы каждый MachineClass выставлял и удалял свое собственное наблюдаемое.

12.02.2015
  • Кажется, это ответ! Спасибо... но это не так просто реализовать, и я не могу понять, как применить ваш пример к моему коду. 12.02.2015
  • Спасибо, я понял то, что упустил раньше! 12.02.2015

  • 2

    Я использовал это, чтобы с некоторым успехом опровергнуть события:

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

    использование

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

    Возможно, он не такой надежный, как в RX, но его легко понять и использовать.

    Последние действия от 03 февраля 2020 г.

    Пересмотрено решение @collie с использованием токенов отмены следующим образом.

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

    Примечания:

    • Вызова Cancel достаточно, чтобы избавиться от CTS
    • Успешно завершенный CTS не отменяется/удаляется до следующего вызова.
    • Как отметил @collie, задачи удаляются, поэтому нет необходимости вызывать Dispose для задачи.

    Раньше я не работал с токенами отмены и, возможно, неправильно их использую.

    07.04.2015
  • Как вы его использовали? 15.02.2017
  • Слик, мне потребовалось некоторое время, чтобы заметить, как вы отменяете уже запущенное действие :-). Однако у этого подхода есть проблема: у вас нет обратной связи/контроля над вашим дебаунсером, поэтому вы не контролируете, когда все действия завершатся. Это проблематично, когда, например, вы удаляете свой основной объект и не понимаете, что после удаления будет выполнено отмененное действие. 27.09.2018
  • См. stackoverflow.com/a/59296962/545233 для версии, которая использует токены отмены для очистки неиспользуемых задач. 12.12.2019
  • IsCompletedSuccessfully доступен только в .NET Core. Вместо этого вы можете использовать !t.IsCanceled, чтобы код также работал в .NET Framework. 22.02.2020

  • 3

    Недавно я занимался обслуживанием приложения, предназначенного для более старой версии .NET Framework (v3.5).

    Я не мог использовать ни Reactive Extensions, ни Task Parallel Library, но мне нужен был хороший, чистый, согласованный способ устранения дребезга событий. Вот что я придумал:

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

    Вот пример его использования в форме Windows с текстовым полем поиска:

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

    4

    У меня возникли проблемы с этим. Я попробовал каждый из ответов здесь, и, поскольку я работаю в универсальном приложении Xamarin, мне, похоже, не хватает некоторых вещей, которые требуются в каждом из этих ответов, и я не хотел добавлять больше пакетов или библиотек. Мое решение работает именно так, как я ожидал, и у меня не возникло никаких проблем с ним. Надеюсь, это поможет кому-то.

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace OrderScanner.Models
    {
        class Debouncer
        {
            private List<CancellationTokenSource> StepperCancelTokens = new List<CancellationTokenSource>();
            private int MillisecondsToWait;
            private readonly object _lockThis = new object(); // Use a locking object to prevent the debouncer to trigger again while the func is still running
    
            public Debouncer(int millisecondsToWait = 300)
            {
                this.MillisecondsToWait = millisecondsToWait;
            }
    
            public void Debouce(Action func)
            {
                CancelAllStepperTokens(); // Cancel all api requests;
                var newTokenSrc = new CancellationTokenSource();
                lock (_lockThis)
                {
                    StepperCancelTokens.Add(newTokenSrc);
                }
                Task.Delay(MillisecondsToWait, newTokenSrc.Token).ContinueWith(task => // Create new request
                {
                    if (!newTokenSrc.IsCancellationRequested) // if it hasn't been cancelled
                    {
                        CancelAllStepperTokens(); // Cancel any that remain (there shouldn't be any)
                        StepperCancelTokens = new List<CancellationTokenSource>(); // set to new list
                        lock (_lockThis)
                        {
                            func(); // run
                        }
                    }
                }, TaskScheduler.FromCurrentSynchronizationContext());
            }
    
            private void CancelAllStepperTokens()
            {
                foreach (var token in StepperCancelTokens)
                {
                    if (!token.IsCancellationRequested)
                    {
                        token.Cancel();
                    }
                }
            }
        }
    }
    

    Называется так...

    private Debouncer StepperDeboucer = new Debouncer(1000); // one second
    
    StepperDeboucer.Debouce(() => { WhateverMethod(args) });
    

    Я бы не рекомендовал это для чего-либо, где машина может отправлять сотни запросов в секунду, но для пользовательского ввода это работает превосходно. Я использую его на степпере в приложении для Android/IOS, которое на шаге вызывает API.

    21.12.2017
  • Я не понимаю, как это должно работать. Кажется, что если Debounce get вызывается чаще, чем MillisecondsToWait milis, код никогда не будет выполняться. Я что-то упускаю? 31.10.2018
  • Ты пробовал это? Отлично работает в моих реализациях. Просто установите время отката примерно на 2000 и отладьте, чтобы увидеть, как это работает. 31.10.2018
  • При работе в узком цикле я не получаю вывода. while (true) { deBounce.Debounce(() => Console.WriteLine(DateTime.UtcNow.ToString("o")));} 31.10.2018
  • О, ну, конечно, вы не получаете выход, это дебаунс, а не дроссель. Debounce ожидает, пока входные события не прекратятся в течение определенного периода времени, прежде чем запускать функцию. Если вам нужен дроссель (запускайте его столько раз за определенное время), это не то решение, которое вам нужно. 31.10.2018
  • На практике, скажем, у вас есть дорогой или медленный вызов, который выполняется при вводе данных пользователем (например, получение цены при увеличении количества или что-то в этом роде). Вы не хотите запускать вызов каждый щелчок, вы хотите только тогда, когда пользователь перестал щелкать хотя бы на секунду. (щелчок, щелчок, щелчок, стоп) -> 1 секунда -> работает debounce. 31.10.2018
  • Хорошо, это имеет больше смысла. Спасибо за разъяснения. Я совершенно неправильно понял цель кода. 31.10.2018
  • Если вы используете это для взаимодействия с событиями, исходящими из потока пользовательского интерфейса (как обычно), стоит добавить TaskScheduler.FromCurrentSynchronizationContext() ко второму параметру вызова .ContinueWith. В противном случае вам придется сделать еще один вызов, чтобы вернуть потоки обратно в пользовательский интерфейс, что снижает производительность. См.: stackoverflow.com/questions/4331262/ 02.09.2020

  • 5

    RX, вероятно, самый простой выбор, особенно если вы уже используете его в своем приложении. Но если нет, добавление этого может быть излишним.

    Для приложений на основе пользовательского интерфейса (например, WPF) я использую следующий класс, использующий DispatcherTimer:

    public class DebounceDispatcher
    {
        private DispatcherTimer timer;
        private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
    
        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();
        }
    }
    

    Чтобы использовать его:

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

    События клавиш теперь обрабатываются только после того, как клавиатура простаивает в течение 200 мс — все предыдущие ожидающие события отбрасываются.

    Существует также метод Throttle, который всегда запускает события после заданного интервала:

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

    6

    Ответ Панайотиса, безусловно, правильный, однако я хотел привести более простой пример, так как мне потребовалось некоторое время, чтобы разобраться, как заставить его работать. Мой сценарий заключается в том, что пользователь вводит в поле поиска, и когда пользователь вводит, мы хотим сделать вызовы API для возврата поисковых предложений, поэтому мы хотим отменить вызовы API, чтобы они не делали один каждый раз, когда они вводят символ.

    Я использую Xamarin.Android, однако это должно относиться к любому сценарию С#...

    private Subject<string> typingSubject = new Subject<string> ();
    private IDisposable typingEventSequence;
    
    private void Init () {
                var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
                searchText.TextChanged += SearchTextChanged;
                typingEventSequence = typingSubject.Throttle (TimeSpan.FromSeconds (1))
                    .Subscribe (query => suggestionsAdapter.Get (query));
    }
    
    private void SearchTextChanged (object sender, TextChangedEventArgs e) {
                var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
                typingSubject.OnNext (searchText.Text.Trim ());
            }
    
    public override void OnDestroy () {
                if (typingEventSequence != null)
                    typingEventSequence.Dispose ();
                base.OnDestroy ();
            }
    

    Когда вы впервые инициализируете экран/класс, вы создаете свое событие для прослушивания ввода пользователем (SearchTextChanged), а затем также настраиваете подписку на регулирование, которая привязана к «typingSubject».

    Затем в событии SearchTextChanged вы можете вызвать typingSubject.OnNext и передать текст поля поиска. По истечении периода отката (1 секунда) будет вызвано событие с подпиской (в нашем случае это suggestionsAdapter.Get).

    Наконец, когда экран закрыт, обязательно избавьтесь от подписки!

    29.08.2016

    7

    Эта маленькая жемчужина вдохновлена ​​дьявольски гениальной попыткой Майка Уорда расширения. Тем не менее, этот достаточно хорошо убирает за собой.

    public static Action Debounce(this Action action, int milliseconds = 300)
    {
        CancellationTokenSource lastCToken = null;
    
        return () =>
        {
            //Cancel/dispose previous
            lastCToken?.Cancel();
            try { 
                lastCToken?.Dispose(); 
            } catch {}          
    
            var tokenSrc = lastCToken = new CancellationTokenSource();
    
            Task.Delay(milliseconds).ContinueWith(task => { action(); }, tokenSrc.Token);
        };
    }
    

    Примечание: в этом случае нет необходимости избавляться от задачи. Доказательства см. здесь.

    Использование

    Action DebounceToConsole;
    int count = 0;
    
    void Main()
    {
        //Assign
        DebounceToConsole = ((Action)ToConsole).Debounce(50);
    
        var random = new Random();
        for (int i = 0; i < 50; i++)
        {
            DebounceToConsole();
            Thread.Sleep(random.Next(100));
        }
    }
    
    public void ToConsole()
    {
        Console.WriteLine($"I ran for the {++count} time.");
    }
    
    12.12.2019
  • Неплохая идея. Нужен ли забор вокруг части Cancel/Dispose/new/assign? Кроме того, хорошая ссылка на задачу dispose. Я всегда беспокоюсь о задачах зомби. Думаю, теперь я могу меньше волноваться. Спасибо. 12.12.2019
  • Хм. Изучая документы CancellationTokenSource, очевидно, что они потокобезопасны, за исключением Dispose(), который должен использоваться только после завершения всех других операций с объектом CancellationTokenSource. . Я считаю, что в этом случае безопасно использовать Dispose() после Cancel(). Однако они рекомендуют, чтобы Dispose(), используемый в этом контексте, был обернут в Try-Catch. Я добавлю это. 15.12.2019

  • 8

    Это вдохновлено основанным на Task.Delay классом Debouncer Ниеминена. Упрощен, небольшие исправления, и должен лучше убирать за собой.

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

    Использование

    private Debouncer debouncer = new Debouncer(500); //1/2 a second
    ...
    debouncer.Debounce(SomeAction);
    
    12.12.2019
  • Это круто. Я написал еще один ответ на основе вашего решения, в котором используется уникальный строковый ключ для регулирования (а не фактический Action), полезный для веб-приложений stackoverflow.com /a/64867741/56621 17.11.2020
  • Я написал тот, который является асинхронным без продолжений - подходит для использования в aspnet. 05.03.2021

  • 9

    Мне нужен был метод Debounce для Blazor, и я постоянно возвращался на эту страницу, поэтому я хотел поделиться своим решением на случай, если оно поможет другим.

    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) { }
        }
    }
    

    Пример вызова функции поиска

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

    10

    Просто запомните последний хит:

    DateTime latestHit = DatetIme.MinValue;
    
    private void eventRxVARxH(MachineClass Machine)
    {
        log.Debug("Event fired");
        if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
        {
            // ignore second hit, too fast
            return;
        }
        latestHit = DateTime.Now;
        // it was slow enough, do processing
        ...
    }
    

    Это позволит провести второе событие, если после последнего события прошло достаточно времени.

    Обратите внимание: невозможно (простым способом) обработать событие last в серии быстрых событий, потому что вы никогда не знаете, какое из них является последним.. .

    ...если только вы не готовы обработать последнее событие пакета, которое произошло очень давно. Затем вы должны запомнить последнее событие и зарегистрировать его, если следующее событие будет достаточно медленным:

    DateTime latestHit = DatetIme.MinValue;
    Machine historicEvent;
    
    private void eventRxVARxH(MachineClass Machine)
    {
        log.Debug("Event fired");
    
        if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
        {
            // ignore second hit, too fast
            historicEvent = Machine; // or some property
            return;
        }
        latestHit = DateTime.Now;
        // it was slow enough, do processing
        ...
        // process historicEvent
        ...
        historicEvent = Machine; 
    }
    
    12.02.2015
  • Я хочу наоборот: я хочу считать последнего последним, а не первым. 12.02.2015
  • Я, может быть, не то, что последнее, но я могу подождать, чтобы сделать это событие с таймером, и если ничего не происходит в течение 1-2 секунд, я не прерываю таймер и позволяю обратному вызову делать свою работу. В противном случае срабатывает другое событие, я должен сбросить таймер и снова начать ждать 1-2 секунды. 12.02.2015
  • Без таймера это может работать, только если вы получите другое событие XYZ мс после последнего 12.02.2015

  • 11

    Я придумал это в своем определении класса.

    Я хотел немедленно запустить свое действие, если в течение определенного периода времени (3 секунды в примере) не было никаких действий.

    Если что-то произошло за последние три секунды, я хочу отправить последнее, что произошло за это время.

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

    Итак, если я разделю свои часы на каждую четверть секунды

      TIME:  1e&a2e&a3e&a4e&a5e&a6e&a7e&a8e&a9e&a0e&a
      EVENT:  A         B    C   D  E              F  
    OBSERVED: A           B           E            F
    

    Обратите внимание, что не предпринимается никаких попыток отменить задачу досрочно, поэтому действия могут накапливаться в течение 3 секунд, прежде чем в конечном итоге станут доступными для сборки мусора.

    28.09.2018
  • Я попробовал это, так как мне понравилось, насколько чистым был код, но это приводит к проблемам с производительностью при закрытии приложения, есть идеи, почему это может быть? Больше ничего не менял, кроме добавления этого кода. 23.10.2019
  • Если есть задержка при выключении запрошенного времени, скажем, 3 секунды, вы можете передать один и тот же токен отмены всем вызовам Task.Delay и установить токен отмены при завершении работы. Обратите внимание, что отмена также вызовет исключение, с которым вам придется иметь дело в Task.Delay. 24.10.2019

  • 12

    Выяснил, как использовать пакет System.Reactive NuGet для правильного устранения дребезга в TextBox.

    На уровне класса у нас есть поле

    private IObservable<EventPattern<TextChangedEventArgs>> textChanged;
    

    Затем, когда мы хотим начать слушать событие:

    // Debouncing capability
    textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
    textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
        Debug.WriteLine("bounce!");
    });
    

    Убедитесь, что вы также не подключаете текстовое поле к обработчику событий. Приведенная выше лямбда — это обработчик событий.

    26.08.2020

    13

    Мне нужно было что-то подобное, но в веб-приложении, поэтому я не могу хранить Action в переменной, он будет потерян между http-запросами.

    Основываясь на других ответах и ​​идее @Collie, я создал класс, который просматривает уникальный строковый ключ для регулирования.

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

    Использование:

    //throttle for 5 secs if it's already been called with this KEY
    Debouncer.Debounce("Some-Unique-ID", () => SendEmails(), 5);
    

    В качестве дополнительного бонуса, поскольку он основан на строковом ключе, вы можете использовать встроенные лямбда-выражения

    Debouncer.Debounce("Some-Unique-ID", () => 
    {
        //do some work here
    }, 5);
    
    17.11.2020

    14

    Я написал асинхронный дебаунсер, который не работает асинхронно-синхронно.

    public sealed class Debouncer : IDisposable {
    
      public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);
    
      private readonly TimeSpan _delay;
      private CancellationTokenSource? previousCancellationToken = null;
    
      public async Task Debounce(Action action) {
        _ = action ?? throw new ArgumentNullException(nameof(action));
        Cancel();
        previousCancellationToken = new CancellationTokenSource();
        try {
          await Task.Delay(_delay, previousCancellationToken.Token);
          await Task.Run(action, previousCancellationToken.Token);
        }
        catch (TaskCanceledException) { }    // can swallow exception as nothing more to do if task cancelled
      }
    
      public void Cancel() {
        if (previousCancellationToken != null) {
          previousCancellationToken.Cancel();
          previousCancellationToken.Dispose();
        }
      }
    
      public void Dispose() => Cancel();
    
    }
    

    Я использую его для отмены сообщений об изменениях файлов, см. полный пример здесь.

    05.03.2021

    15

    Меня вдохновил ответ Майка, но мне нужно было решение, которое работало бы без задач, которое просто проглатывало бы последующие вызовы событий до тех пор, пока не истечет время ожидания отмены. Вот мое решение:

    public static Action<T> Debounce<T>(this Action<T> action, int milliseconds = 300)
    {
        DateTime? runningCallTime = null;
        var locker = new object();
    
        return arg =>
        {
            lock (locker)
            {
                if (!runningCallTime.HasValue ||
                    runningCallTime.Value.AddMilliseconds(milliseconds) <= DateTime.UtcNow)
                {
                    runningCallTime = DateTime.UtcNow;
                    action.Invoke(arg);
                }
            }
        };
    
    }
    
    22.06.2021

    16

    Я знаю, что опоздал на эту вечеринку на пару сотен тысяч минут, но решил добавить свои 2 цента. Я удивлен, что никто не предложил это, поэтому я предполагаю, что есть что-то, чего я не знаю, что может сделать его менее идеальным, поэтому, возможно, я узнаю что-то новое, если это будет сбито. Я часто использую решение, в котором используется метод System.Threading.Timer Change().

    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*/
    }
    
    21.01.2019
  • Таймер немного излишен для того, что необходимо, другие примеры выполняют код только по мере необходимости, тогда как этот продолжает выполнять код независимо от любых происходящих событий. 23.10.2019
  • Это не постоянно выполняет код. Когда вызывается TextChanged, он планирует вызвать метод saveOrWhatever через 3 секунды. Если TextChanged вызывается снова до вызова saveOrWhatever, он сбрасывает таймер, поэтому он будет вызван только через 3 секунды после последнего вызова TextChanged. Второй параметр Change объекта таймера — это частота повторного срабатывания. Если установлено значение бесконечно, он не будет перезапускаться после первого выполнения кода. 24.10.2019
  • Он непрерывно выполняет таймер. 24.10.2019
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..