3

I've seen a number of posts about limiting the number of tasks at a time (System.Threading.Tasks - Limit the number of concurrent Tasks is a good one).

However, I need to limit the number of tasks by second - only X number of tasks per second? Is there an easy way to accomplish this?

I thought about creating a ConcurrentDictionary, the key being the current second, and the second being the count so far. The doing a check if we are at 20 for the current second, then stop. This seems suboptimal.

I'd rather do something like spin up a task every 1s/20. Any thoughts?

Community
  • 1
  • 1
Prescott
  • 7,312
  • 5
  • 49
  • 70
  • What if some tasks take more than 1/20s. Should it create a new task? – I4V Sep 12 '13 at 18:29
  • Good question, I think for my purposes, 1/20s is sufficent, but if there is an elegant solution for that, that'd be great – Prescott Sep 12 '13 at 18:58

1 Answers1

3

I think, this can be a starting point. Below sample creates 50 Tasks (running 5 tasks/sec).

This doesn't block the code that creates the tasks. If you want to block the caller until all task have been scheduled, then you can use Task.Delay((int)shouldWait).Wait() in QueueTask

TaskFactory taskFactory = new TaskFactory(new TimeLimitedTaskScheduler(5));

for (int i = 0; i < 50; i++)
{
    var x = taskFactory.StartNew<int>(() => DateTime.Now.Second)
                        .ContinueWith(t => Console.WriteLine(t.Result));
}

Console.WriteLine("End of Loop");

public class TimeLimitedTaskScheduler : TaskScheduler
{
    int _TaskCount = 0;
    Stopwatch _Sw = null;
    int _MaxTasksPerSecond;

    public TimeLimitedTaskScheduler(int maxTasksPerSecond)
    {
        _MaxTasksPerSecond = maxTasksPerSecond;
    }

    protected override void QueueTask(Task task)
    {
        if (_TaskCount == 0) _Sw = Stopwatch.StartNew();

        var shouldWait = (1000 / _MaxTasksPerSecond) * _TaskCount - _Sw.ElapsedMilliseconds;

        if (shouldWait < 0)
        {
            shouldWait = _TaskCount = 0;
            _Sw.Restart();
        }

        Task.Delay((int)shouldWait)
            .ContinueWith(t => ThreadPool.QueueUserWorkItem((_) => base.TryExecuteTask(task)));

        _TaskCount++;
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return base.TryExecuteTask(task);
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        throw new NotImplementedException();
    }


}
I4V
  • 34,891
  • 6
  • 67
  • 79
  • I have a curious problem - this mostly works well. From time to time, it blows up. When I set max per second to say 5, it's great for about a minute, and then all of the sudden it spikes to say 20-40 for a few seconds, then drops, and then spikes again shortly there after (in a two minute test) – Prescott Sep 23 '13 at 22:18
  • @Prescott Because This algorithm tries to keep the *N tasks/sec* in a long term. Since we don't know how many time a task would take before it has completed, it would be incorrect to adjust the algorithm to start N tasks per second (which would result lesser task then that can be started) – I4V Sep 23 '13 at 22:29
  • I understand what you are saying, but I'm not seeing what part of the code make sure it's "in a long term". I'd actually prefer to start N Tasks per second, and not worry about how long they take - if the tasks max out the system and it gets even slower, that's fine. My goal is to say "no more than 10 tasks a second ever" 9 or 8 or 7 is fine, but never 11. How would you recommend changing this code to accomplish that? (I thought it did, but I might have gotten a headache on the `shouldWait` logic :) ) – Prescott Sep 23 '13 at 22:42
  • @Prescott here is 1:40 AM, I'll give it a try tomorrow. – I4V Sep 23 '13 at 22:44
  • I appreciate the assistance, I 'll work on it myself and post any updates – Prescott Sep 23 '13 at 22:46