C # событие debounce

Вы можете хранить сеанс там, где хотите.

Пример:

:mksession! D:/session.ses

Здесь хранится сеанс в драйвере D.

Это может быть открыто набрав

:so D:/session.ses

в любом из файлов vim.

29
задан g t 12 February 2015 в 08:30
поделиться

9 ответов

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

Реактивные расширения в .NET 4.5 были созданы для обработки именно этих сценариев. Вы можете легко использовать их для обеспечения такой функциональности с помощью таких методов, как Throttle , Buffer , Window или Sample . Вы публикуете события в субъекте , применяете к нему одну из оконных функций, например, чтобы получать уведомление, только если не было активности в течение X секунд или событий Y, а затем подписываетесь на уведомление.

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

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

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

Когда ваш код получает событие, вам нужно только опубликовать его в теме с помощью 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 и т. Д., Добавлять задержки и многое другое . [+1122]

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

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

Введение в 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 выставлял и располагал свою собственную наблюдаемую.

34
ответ дан Matthew Bonner 12 February 2015 в 08:30
поделиться

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

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, но это легко понять и использовать.

33
ответ дан Kingpin2k 12 February 2015 в 08:30
поделиться

Недавно я занимался обслуживанием приложения, которое предназначалось для более старой версии .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();
                }
            }
        }
    }
}

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

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
        }
    }
}
7
ответ дан Ronnie Overby 12 February 2015 в 08:30
поделиться

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;            
    }
2
ответ дан Rick Strahl 12 February 2015 в 08:30
поделиться

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

Я хотел немедленно запустить свое действие, если в течение периода времени не было никаких действий (в примере 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 секунды до того, как он станет доступен для сборки мусора.

0
ответ дан Anthony Wieser 12 February 2015 в 08:30
поделиться

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

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

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 секунда) он вызовет подписанное событие (предположения, Adapter.Get в нашем случае.)

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

5
ответ дан Justin 12 February 2015 в 08:30
поделиться

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

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

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

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

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

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; 
}
1
ответ дан DrKoch 12 February 2015 в 08:30
поделиться

Я столкнулся с проблемами с этим. Я попробовал каждый из ответов здесь, и, поскольку я нахожусь в универсальном приложении 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
                    }
                }
            });
        }

        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 на шаге.

4
ответ дан Nieminen 12 February 2015 в 08:30
поделиться

Я знаю, что опоздал на эту вечеринку на пару сотен тысяч минут, но я решил добавить свои 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*/
}
0
ответ дан HDL_CinC_Dragon 12 February 2015 в 08:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: