using System.Threading.Tasks;
public static void Main()
RateLimiter rateLimiter = new(2, TimeSpan.FromMilliseconds(500));
Task<int>[] tasks = Enumerable.Range(1, 10).Select(async item =>
Print($"Item #{item} before rateLimiter.WaitAsync");
await rateLimiter.WaitAsync();
Print($"Processing #{item}");
int[] results = Task.WhenAll(tasks).Result;
Print($"Results: {String.Join(", ", results)}");
private readonly SemaphoreSlim _semaphore;
private readonly TimeSpan _timeUnit;
public RateLimiter(int maxActionsPerTimeUnit, TimeSpan timeUnit)
if (maxActionsPerTimeUnit < 1)
throw new ArgumentOutOfRangeException(nameof(maxActionsPerTimeUnit));
if (timeUnit < TimeSpan.Zero || timeUnit.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException(nameof(timeUnit));
_semaphore = new SemaphoreSlim(maxActionsPerTimeUnit, maxActionsPerTimeUnit);
public async Task WaitAsync(CancellationToken cancellationToken = default)
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
System.Threading.Timer timer = new(_ => _semaphore.Release());
try { timer.Change(_timeUnit, Timeout.InfiniteTimeSpan); }
catch { _semaphore.Release(); throw; }
private static void Print(object value)
Console.WriteLine($@"{DateTime.Now:HH:mm:ss.fff} [{Thread.CurrentThread.ManagedThreadId}] > {value}");