C#事件去抖动
我正在收听一条硬件事件消息,但我需要对其进行去抖动,以避免太多查询C#事件去抖动,c#,events,debouncing,C#,Events,Debouncing,我正在收听一条硬件事件消息,但我需要对其进行去抖动,以避免太多查询 这是一个发送机器状态的硬件事件,出于统计目的,我必须将其存储在数据库中,有时它的状态经常发生变化(闪烁?)。在这种情况下,我只想存储一个“稳定”状态,我想通过在将状态存储到数据库之前等待1-2秒来实现它 这是我的代码: private MachineClass connect() { try { MachineClass rpc = new MachineClass(); rpc.
这是一个发送机器状态的硬件事件,出于统计目的,我必须将其存储在数据库中,有时它的状态经常发生变化(闪烁?)。在这种情况下,我只想存储一个“稳定”状态,我想通过在将状态存储到数据库之前等待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");
}
我将这种行为称为“去抖动”:等待几次以真正完成其工作:如果在去抖动时间内再次触发相同的事件,我必须取消第一个请求,并开始等待去抖动时间以完成第二个事件 管理它的最佳选择是什么?只是一个一次性计时器 要解释“去盎司”功能,请参见以下关键事件的javascript实现:
只要记住最新的“热门歌曲:
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;
私有void eventRxVARxH(MachineClass Machine)
{
log.Debug(“触发事件”);
if(latestHit-DateTime.Now
如果在最后一个事件之后有足够的时间,这将允许第二个事件
请注意:不可能(以简单的方式)处理一系列快速事件中的最后一个事件,因为您永远不知道哪一个是最后一个
…除非您准备处理很久以前的突发事件的最后一个事件。然后,如果下一个事件足够慢,您必须记住最后一个事件并记录它:
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;
}
DateTime latestHit=DateTime.MinValue;
机器历史事件;
私有void eventRxVARxH(MachineClass Machine)
{
log.Debug(“触发事件”);
if(latestHit-DateTime.Now
这不是一个简单的从头开始编写代码的请求,因为有几个细微差别。类似的场景是监视FileSystemWatcher,并在尝试打开修改过的文件之前,等待一个大拷贝后事情平静下来
.NET 4.5中创建的反应式扩展正是为了处理这些场景。您可以轻松地使用它们,通过以下方法提供此类功能:或。将事件发布到,对其应用一个窗口功能,例如,仅在X秒或Y事件中没有活动时才获取通知,然后订阅“否”美化
Subject<MyEventData> _mySubject=new Subject<MyEventData>();
....
var eventSequenc=mySubject.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(events=>MySubscriptionMethod(events));
如果您的硬件事件显示为典型的.NET事件,则可以绕过主题并手动发布,如图所示:
var mySequence=Observable.FromEventPattern(
h=>\u myDevice.MyEvent+=h,
h=>\u myDevice.MyEvent-=h);
_mySequence.节流阀(时间跨度从秒(1))
.Subscribe(events=>MySubscriptionMethod(events));
您还可以从任务中创建可观察对象,将事件序列与LINQ操作符组合以请求,例如:使用Zip对不同的硬件事件,使用另一个事件源绑定节流/缓冲区等,添加延迟等等
被动扩展是作为一个组件提供的,因此很容易将它们添加到您的项目中
斯蒂芬·克利里(Stephen Cleary)的书“”是一本关于反应式扩展的非常好的资源,并解释了如何使用它以及它如何与.NET中的其他并发API(如任务、事件等)相适应
是一个优秀的系列文章(这是我复制样本的地方),有几个例子
更新
使用您的特定示例,您可以执行以下操作:
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;
}
ioobservable\u myObservable;
专用计算机类连接()
{
MachineClass rpc=新MachineClass();
_可观察的
.FromEventPattern(
h=>rpc.RxVARxH+=h,
h=>rpc.RxVARxH-=h)
.节气门(时间跨度从秒(1));
_Subscribe(machine=>eventRxVARxH(machine));
返回rpc;
}
当然,这可以得到极大的改进-可观察到的和订阅都需要在某一点上进行处理。此代码假设您只控制一个设备。如果您有多个设备,则可以在类内创建可观察到的,以便每个MachineClass公开和处理其自己的可观察到的。我已经使用此方法来解除Bounce一些成功的活动:
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();
});
};
}
public静态动作反Bounce(此动作func,int毫秒=300)
{
var last=0;
返回arg=>
{
无功电流=联锁增量(参考最后一次);
Task.Delay(毫秒)。ContinueWith(Task=>
{
如果(当前==上次)函数(参数);
task.Dispose();
});
};
}
用法
动作a=(arg)=>
{
//这是成功的取消公告。。。
控制台写入线(arg);
};
var debouncedWrapper=a.Debounce();
while(true)
{
var rndVal=下一个(400)rnd;
睡眠(rndVal);
去BouncedWrapper(rndVal);
}
它可能不像RX中的那样强大,但易于理解和使用
后续行动2020-02-03
使用取消令牌修改@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);
};
}
public静态动作反Bounce(此动作func,int毫秒=300)
{
CancellationTokenSource?cancelTokenSource=null;
返回arg=>
{
cancelTokenSource?.Cancel();
cancelTokenSource=新的CancellationTokenSource();
Task.Delay(毫秒,cancelTokenSource.Token)
.ContinueWith(t=>
{
如果(t.IsCompletedSuccessfully)
{
func(arg);
}
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);
}
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);
};
}
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 ();
}
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();
});
}
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;
}
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
}
}
}
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) });
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
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*/
}
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.");
}
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);
private IObservable<EventPattern<TextChangedEventArgs>> textChanged;
// Debouncing capability
textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
Debug.WriteLine("bounce!");
});
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);