Rate Limiting in C#
Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. C# supports rate limiting using tasks, channels, and timers.
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class RateLimiting
{
static async Task Main()
{
// First we'll look at basic rate limiting. Suppose
// we want to limit our handling of incoming requests.
// We'll serve these requests off a channel of the
// same name.
var requests = new BlockingCollection<int>();
for (int i = 1; i <= 5; i++)
{
requests.Add(i);
}
requests.CompleteAdding();
// This timer will trigger every 200 milliseconds.
// This is the regulator in our rate limiting scheme.
using var limiter = new Timer(_ => { }, null, 0, 200);
// By waiting for the timer to elapse before serving each request,
// we limit ourselves to 1 request every 200 milliseconds.
foreach (var req in requests.GetConsumingEnumerable())
{
await Task.Delay(200);
Console.WriteLine($"request {req} {DateTime.Now}");
}
// We may want to allow short bursts of requests in
// our rate limiting scheme while preserving the
// overall rate limit. We can accomplish this by
// using a semaphore. This burstyLimiter
// will allow bursts of up to 3 events.
using var burstyLimiter = new SemaphoreSlim(3, 3);
// Every 200 milliseconds we'll try to add a new
// permit to burstyLimiter, up to its limit of 3.
_ = Task.Run(async () =>
{
while (true)
{
await Task.Delay(200);
burstyLimiter.Release(1);
}
});
// Now simulate 5 more incoming requests. The first
// 3 of these will benefit from the burst capability
// of burstyLimiter.
var burstyRequests = new BlockingCollection<int>();
for (int i = 1; i <= 5; i++)
{
burstyRequests.Add(i);
}
burstyRequests.CompleteAdding();
foreach (var req in burstyRequests.GetConsumingEnumerable())
{
await burstyLimiter.WaitAsync();
Console.WriteLine($"request {req} {DateTime.Now}");
}
}
}
Running our program we see the first batch of requests handled once every ~200 milliseconds as desired.
request 1 5/26/2023 10:30:00 AM
request 2 5/26/2023 10:30:00 AM
request 3 5/26/2023 10:30:00 AM
request 4 5/26/2023 10:30:01 AM
request 5 5/26/2023 10:30:01 AM
For the second batch of requests we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each.
request 1 5/26/2023 10:30:01 AM
request 2 5/26/2023 10:30:01 AM
request 3 5/26/2023 10:30:01 AM
request 4 5/26/2023 10:30:01 AM
request 5 5/26/2023 10:30:01 AM
In this C# version, we use BlockingCollection<T>
to simulate channels, Timer
for regular intervals, and SemaphoreSlim
for the bursty limiter. The Task.Delay
method is used to introduce delays, and async/await
is used for asynchronous operations.