Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to throttle the speed of an event without using Rx Framework

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.

like image 380
user3064847 Avatar asked Jan 28 '14 08:01

user3064847


3 Answers

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.

like image 196
James World Avatar answered Nov 11 '22 13:11

James World


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));
like image 37
Theodor Zoulias Avatar answered Nov 11 '22 13:11

Theodor Zoulias


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));
like image 38
BTownTKD Avatar answered Nov 11 '22 12:11

BTownTKD