6
\$\begingroup\$

I have a scenario where my BaseRepository gets a lot of commands in a short time period. At other moments, this doesn't happen. What I want to achieve is to throttle these commands. I.e., when a command must be executed, wait for a short period (0.2s) for any other calls. If none are made, execute the command (go to the database). If others are made, reset the timer. This way, after all commands have been added, we can execute all the commands over one connection.

This is how I implemented it, and I was wondering for any comments:

public BaseRepository()
{
    _throttledActions = new List<Action<Entities>>();
    _timer = new Timer(ExecuteThrottledActions);
}

protected void Execute(Action<Entities> action)
{
    lock (_locker)
    {
        _throttledActions.add(action);
    }

    _timer.Change(TimeSpan.FromSeconds(0.2), TimeSpan.FromMilliseconds(-1));
}

private void ExecuteThrottledActions(object state)
{
    var throttledActionsCopy = new List<Action<Entities>>();
    lock (_locker)
    {
        throttledActionsCopy.AddRange(_throttledActions);
        _throttledActions.Clear();
    }

    using (var scope...)
    {
        using (var entities = new Entities())
        {
            foreach (var throttledAction in throttledActionsCopy)
            {
                throttledAction(entities);
            }
        }
    }
}

This allows me to do the following in a class that inherits from the BaseRepository:

public void Save(Customer customer)
{
    Executed(e => e.Customers.AddObject(customer);
}

And if lots of calls are made to this Save method, I'll save them up and execute them under one using statement and so under one connection.

In short, what I do is:

  • add the Action to a list
  • start a timer
  • if another Action is added fast enough, add this Action too, and reset the timer
  • after 0.2 seconds, I'll loop over the actions and execute them

I'm copying the Actions to another list (with locking) because I don't want other calls to interfere with the collection that I'm looping over (and clearing after I've executed everything).

Any comments?

\$\endgroup\$
  • 2
    \$\begingroup\$ Good question, hope you get some useful reviews! \$\endgroup\$ – Abbas Jan 19 '15 at 15:23
  • \$\begingroup\$ Have you considered what happens if you are wildly successful and you keep receiving events in < 0.2s intervals? \$\endgroup\$ – RobH Jan 19 '15 at 16:28
  • 1
    \$\begingroup\$ Vaguely related: A while ago I made a generic class which does that kind of thing (buffers fast changing events and then releases them in a controlled fashion): codereview.stackexchange.com/questions/66918/… \$\endgroup\$ – ChrisWue Jan 19 '15 at 19:02
2
\$\begingroup\$

There is one big issue: if you keep getting requests then it is possible to starve the executor.

You can fix that by either testing the size of the list and if it becomes too large then don't reset the timer,

protected void Execute(Action<Entities> action)
{
    bool resetTimer;
    lock (_locker)
    {
        _throttledActions.add(action);
        reset = _throttledActions.size() < MaxSize;
    }

    if(resetTimer)
        _timer.Change(TimeSpan.FromSeconds(0.2), TimeSpan.FromMilliseconds(-1));
}

Or you can use a second slower timer that doesn't get reset and just runs every so often to purge the list anyway regardless of how long actions have been waiting and how many may still follow.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I added the code you proposed. Indeed, it was a case that I wasn't handling. \$\endgroup\$ – Peter Jan 20 '15 at 13:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.