Skip to content

Instantly share code, notes, and snippets.

@tmenier
Created January 31, 2020 13:32
Show Gist options
  • Save tmenier/4c2571a4e3ea99c2dead97e416f8a4a4 to your computer and use it in GitHub Desktop.
Save tmenier/4c2571a4e3ea99c2dead97e416f8a4a4 to your computer and use it in GitHub Desktop.
Task.WhenAll on steroids
public static class Throttler
{
/// <summary>
/// Concurrently run a task for each data item. Like Task.WhenAll, except you can cap the number allowed to run at a time,
/// and enforce a minimum pause between the start of each.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">A list of data items.</param>
/// <param name="minPause">Minimum wait time in milliseconds between starting tasks.</param>
/// <param name="maxConcurrent">Maximum tasks allowed to run at the same time.</param>
/// <param name="op">An async operation to perform on each data item.</param>
/// <returns></returns>
public static async Task RunAsync<T>(IEnumerable<T> items, int minPause, int maxConcurrent, Func<T, Task> op) {
using (var sem = new SemaphoreSlim(maxConcurrent)) {
async Task RunOneAsync(T item) {
try { await op(item); }
finally { sem.Release(); }
}
var tasks = new List<Task>();
foreach (var item in items) {
if (tasks.Any()) // avoid pausing before the first one
await Task.WhenAll(sem.WaitAsync(), Task.Delay(minPause)); // wait until we're under the concurrency limit AND at least minPause has passed
tasks.Add(RunOneAsync(item));
}
await Task.WhenAll(tasks);
}
}
}
@tmenier
Copy link
Author

tmenier commented Jan 31, 2020

The advantages of this solution over processing items in batches (using Task.WhenAll) is 2-fold:

  1. The ability to add a brief pause before the start of each Task.

  2. The system is better at maintaining the maximum allowed concurrency at all times. For example, if you're processing items in batches of 10, then items 11-20 would start (all at once) only after the number of running Tasks in the first batch is down to zero. With this solution, item 11 is allowed to start as soon as the number of running Tasks is down to 9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment