DEV Community

Ricky Stam
Ricky Stam

Posted on

.NET How to throttle HttpClient Requests using SemaphoreSlim

Last week I had to deal with an external API that didn't allow for more that 50 requests per second.

On my journey on how to deal with this limitation I stumbled upon SemaphoreSlim Class.

SemaphoreSlim limits the number of threads that can access a resource concurrently. It seemed a good fit and guess what... It was! 😛

I won't bother you with more details, let's see a simplified example on how we can limit our concurrent requests to just 3 per second with code:

class Program
    {
        private static HttpClient httpClient = new HttpClient();

        static async Task Main(string[] args)
        {
            var tasks = new List<Task>();

            // here we set the max allowed concurrent requests, in our case it is 3
            var throttler = new SemaphoreSlim(3);

            // Let's say that we have 12 requests in total that we want to send
            for (var i = 0; i < 12; i++)
            {
                // let's wait here until we can pass from the Semaphore
                await throttler.WaitAsync();

                // add our task logig here
                tasks.Add(Task.Run(async () =>
                {
                    try
                    {
                        var result = await ExecuteRequest();

                        // let's wait here for 1 second to honor the API's rate limit                         
                        await Task.Delay(1000);
                    }
                    finally
                    {
                        // here we release the throttler immediately
                        throttler.Release();
                    }
                }));
            }

            // await for all the tasks to complete
            await Task.WhenAll(tasks.ToArray());
        }

        private static async Task<HttpResponseMessage> ExecuteRequest()
        {
            var result = await httpClient.GetAsync("https://www.bing.com");
            return result;
        }
    }

Please let me know in the comments below what is the equivalent of SemaphoreSlim class in other languages.

You can find the full code on my GitHub

Just run the project and use Fiddler or any other network tool to verify the throttled behaviour.

ALWAYS TRUST BUT VERIFY 😁

This post was written with love ❤️

Top comments (4)

Collapse
 
sn3akyp3t3 profile image
Sn3akyP3t3

Maybe someone can describe what is going on when adding this in the code to see which thread is being released.

// here we release the throttler immediately
throttler.Release();
Console.WriteLine(i.ToString());

The results are almost never the same and never seem to iterate in count as I would expect. Any ideas why this happens?

Collapse
 
random_coder_1 profile image
Michael Ayres

i is almost always bigger than you expect? Rather than being passed by value, you're still referencing the shared variable; by the time the tasks start completing and logging, the loop has executed more than expected or completely.

dotnetfiddle.net/5vNKSV

Collapse
 
saltonsolutions profile image
saltonsolutions • Edited

You are the Greatest American Hero Of All Time!
Short, Sweet, Helpful!

I have been working all day with Parallel.Foreach, and other async stuff, but this is working for me!!!

Thanks again! Pat

Collapse
 
rickystam profile image
Ricky Stam

Thank you sooo much, I'm really happy I could assist! 😄