How to throttle the speed of an event without using Rx Framework
This works, if your event is of type EventHandler<EventArgs>
for example. It creates a wrapper for your event handler that is throttled:
private EventHandler<EventArgs> CreateThrottledEventHandler(
EventHandler<EventArgs> handler,
TimeSpan throttle)
{
bool throttling = false;
return (s,e) =>
{
if(throttling) return;
handler(s,e);
throttling = true;
Task.Delay(throttle).ContinueWith(_ => throttling = false);
};
}
Attach like this:
this.SomeEvent += CreateThrottledEventHandler(
(s,e) => Console.WriteLine("I am throttled!"),
TimeSpan.FromSeconds(5));
Although, you should store the handler returned from CreateThrottledEventHandler
if you need to unwire it with -=
later.
I believe the following requirements are essential in a 'throttled' event handler:
- The first event is raised immediately.
- Subsequent events - which occur within the throttling period - are ignored.
- The last event to occur during the throttling period is guaranteed to be raised, once the throttling period has expired.
Considering those requirements, the previously-accepted answer was not satisfactory; it correctly fulfills the first two requirements, but it does not guarantee that the last event will eventually be raised. I think that point is particularly important, because events which are raised with high frequency typically represent 'change of state' and/or 'user requests'; and we always want to receive the last update for changes in state or user interaction.
In an effort to satisfy all these requirements, I created my own generic "ThrottledEventHandler" class.
public class ThrottledEventHandler<TArgs>
where TArgs : EventArgs
{
private readonly EventHandler<TArgs> _innerHandler;
private readonly EventHandler<TArgs> _outerHandler;
private readonly Timer _throttleTimer;
private readonly object _throttleLock = new object();
private Action _delayedHandler = null;
public ThrottledEventHandler(EventHandler<TArgs> handler, TimeSpan delay)
{
_innerHandler = handler;
_outerHandler = HandleIncomingEvent;
_throttleTimer = new Timer(delay.TotalMilliseconds);
_throttleTimer.Elapsed += Timer_Tick;
}
private void HandleIncomingEvent(object sender, TArgs args)
{
lock (_throttleLock)
{
if (_throttleTimer.Enabled)
{
_delayedHandler = () => SendEventToHandler(sender, args);
}
else
{
SendEventToHandler(sender, args);
}
}
}
private void SendEventToHandler(object sender, TArgs args)
{
if (_innerHandler != null)
{
_innerHandler(sender, args);
_throttleTimer.Start();
}
}
private void Timer_Tick(object sender, EventArgs args)
{
lock (_throttleLock)
{
_throttleTimer.Stop();
if (_delayedHandler != null)
{
_delayedHandler();
_delayedHandler = null;
}
}
}
public static implicit operator EventHandler<TArgs>(ThrottledEventHandler<TArgs> throttledHandler)
{
return throttledHandler._outerHandler;
}
}
Usage looks something like this:
myObject.MyFrequentlyRaisedEvent += new ThrottledEventHandler(MyActualEventHandler, TimeSpan.FromMilliseconds(50));