Using SemaphoreSlim to Limit API Calls Per Minute

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;


class Program
{
    private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); // Allows one task at a time to update the counter
    private static readonly int MaxCallsPerMinute = 100;
    private static readonly TimeSpan RateLimitInterval = TimeSpan.FromMinutes(1);


    private static int CallsMade = 0;
    private static DateTime RateLimitReset = DateTime.UtcNow.Add(RateLimitInterval);


    static async Task Main(string[] args)
    {
        string apiUrl = "https://api.example.com/endpoint";


        // Simulate multiple API calls
        var tasks = new Task[200]; // Simulate 200 API requests


        for (int i = 0; i < tasks.Length; i++)
        {
            int requestId = i + 1;
            tasks[i] = MakeRateLimitedCall(apiUrl, requestId);
        }


        // Wait for all tasks to complete
        await Task.WhenAll(tasks);


        Console.WriteLine("All API calls completed.");
    }


    private static async Task MakeRateLimitedCall(string apiUrl, int requestId)
    {
        // Enforce rate-limiting logic
        await Semaphore.WaitAsync();
        try
        {
            await ApplyRateLimit(requestId);


            // Simulate API call
            using (HttpClient client = new HttpClient())
            {
                Console.WriteLine($"Request {requestId}: Started.");
                HttpResponseMessage response = await client.GetAsync(apiUrl);
                Console.WriteLine($"Request {requestId}: Completed with status {response.StatusCode}.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Request {requestId}: Error - {ex.Message}");
        }
        finally
        {
            Semaphore.Release();
        }
    }


    private static Task ApplyRateLimit(int requestId)
    {
        lock (Semaphore)
        {
            if (DateTime.UtcNow >= RateLimitReset)
            {
                // Reset the counters when the time window is up
                CallsMade = 0;
                RateLimitReset = DateTime.UtcNow.Add(RateLimitInterval);
                Console.WriteLine($"Request {requestId}: Rate limit reset.");
            }


            if (CallsMade >= MaxCallsPerMinute)
            {
                var waitTime = (int)(RateLimitReset - DateTime.UtcNow).TotalMilliseconds;
                Console.WriteLine($"Request {requestId}: Rate limit reached, waiting {waitTime / 1000} seconds...");
                Monitor.Wait(Semaphore, waitTime);
            }


            CallsMade++;
        }


        return Task.CompletedTask;
    }
}