Question

I want to throttle the speed of an event, How I can achieve this without using Microsoft Rx framework. I had done this with the help of Rx. But what I am trying is, I need to throttle Map's View changed event based on a time slot. Is it possible to implement the same without using Rx.

I am not allowed to use Rx and I have to keep the binary size as small as possible.

Was it helpful?

Solution

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.

OTHER TIPS

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

Here is a Throttle method, inspired by James World's CreateThrottledEventHandler method, that mimics the behavior of the Rx Throttle/Debounce operator. It propagates only events that come after a dueTime period of inactivity. This means that in case the source events are raised in quick succession, with no time gaps larger than dueTime between them, no event is going to be propagated.

/// <summary>Ignores events that are followed by another event within
/// a specified relative time duration.</summary>
public static EventHandler<TEventArgs> Throttle<TEventArgs>(
    EventHandler<TEventArgs> handler,
    TimeSpan dueTime)
{
    System.Threading.Timer timer = null;
    return (s, e) =>
    {
        var newTimer = new System.Threading.Timer(
            _ => handler(s, e), null, dueTime, Timeout.InfiniteTimeSpan);
        var previousTimer = Interlocked.Exchange(ref timer, newTimer);
        previousTimer?.Dispose();
    };
}

Usage example:

public event EventHandler<int> SomeEvent;

//...

this.SomeEvent += Throttle<int>((s, e) =>
{
    Console.WriteLine($"Received: {e}");
}, TimeSpan.FromSeconds(1.0));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top