C# 信号量slim可处理每个时间段的限制

C# 信号量slim可处理每个时间段的限制,c#,semaphore,throttling,C#,Semaphore,Throttling,我有一个客户要求调用他们的API,但是,由于节流限制,我们一分钟只能调用100个API。我使用SemaphoreSlim来处理这个问题,下面是我的代码 async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls) { var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount:

我有一个客户要求调用他们的API,但是,由于节流限制,我们一分钟只能调用100个API。我使用SemaphoreSlim来处理这个问题,下面是我的代码

 async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls) 
 {
     var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount: 100);
     var failedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();
     var passedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();

     var tasks = locationApiCalls.Select(async locationApiCall =>
     {
          await semaphoreSlim.WaitAsync();
          try
          {
              var response = await RestApi.GetResponseAsync(locationApi);
              if (response.IsSuccessful)
              {
                  passedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
              }
              else
              {
                 failedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
              }
           }
           finally
           {
               semaphoreSlim.Release();
           }
     });

     await Task.WhenAll(tasks);
     var passedResponsesList = passedResponses.SelectMany(x => x).ToList();
}

从不执行,我也看到很多失败的响应,我想我必须在代码中的某个地方添加Task.Delay(延迟1分钟)。

您需要跟踪前100个请求的执行时间。在下面的示例实现中,
ConcurrentQueue
记录了前100个请求中每个请求的相对完成时间。通过从该队列中第一次(因此也是最早的)退出队列,您可以检查自100个请求之后经过了多少时间。如果不到一分钟,则下一个请求需要等待一分钟的剩余时间才能执行

async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls)
{
    var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount: 100);
    var total = 0;
    var stopwatch = Stopwatch.StartNew();
    var completionTimes = new ConcurrentQueue<TimeSpan>();

    var failedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();
    var passedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();

    var tasks = locationApiCalls.Select(async locationApiCall =>
    {
        await semaphoreSlim.WaitAsync();

        if (Interlocked.Increment(ref total) > 100)
        {
            completionTimes.TryDequeue(out var earliest);
            var elapsed = stopwatch.Elapsed - earliest;
            var delay = TimeSpan.FromSeconds(60) - elapsed;
            if (delay > TimeSpan.Zero)
                await Task.Delay(delay);
        }

        try
        {
            var response = await RestApi.GetResponseAsync(locationApi);
            if (response.IsSuccessful)
            {
                passedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
            else
            {
                failedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
        }
        finally
        {
            completionTimes.Enqueue(stopwatch.Elapsed);
            semaphoreSlim.Release();
        }
    });

    await Task.WhenAll(tasks);
    var passedResponsesList = passedResponses.SelectMany(x => x).ToList();
}
异步任务GetAllResponses(列表位置Apicalls) { var semaphoreSlim=新的信号量lim(initialCount:100,maxCount:100); var合计=0; var stopwatch=stopwatch.StartNew(); var completionTimes=新的ConcurrentQueue(); var failedResponses=新的ConcurrentBag(); var passedResponses=new ConcurrentBag(); var tasks=locationApiCalls.Select(异步locationApiCalls=> { 等待信号量lim.WaitAsync(); 如果(联锁增量(参考总数)>100) { completionTimes.TryDequeue(最早退出变量); var已用=秒表已用-最早; var delay=从秒(60)开始的时间跨度-已用时间; 如果(延迟>时间跨度为零) 等待任务。延迟(延迟); } 尝试 { var response=await RestApi.GetResponseAsync(locationApi); if(response.issucessful) { passedResponses.Add((IReadOnlyCollection)响应); } 其他的 { failedResponses.Add((IReadOnlyCollection)响应); } } 最后 { completionTimes.Enqueue(秒表运行时间); semaphoreSlim.Release(); } }); 等待任务。何时(任务); var passedResponsesList=passedResponses.SelectMany(x=>x.ToList(); }
如果您是从WinForms或WPF应用程序的UI线程调用此方法,请记住将
ConfigureAwait(false)
添加到其
await
语句中。

您需要跟踪前100个请求执行的时间。在下面的示例实现中,
ConcurrentQueue
记录了前100个请求中每个请求的相对完成时间。通过从该队列中第一次(因此也是最早的)退出队列,您可以检查自100个请求之后经过了多少时间。如果不到一分钟,则下一个请求需要等待一分钟的剩余时间才能执行

async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls)
{
    var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount: 100);
    var total = 0;
    var stopwatch = Stopwatch.StartNew();
    var completionTimes = new ConcurrentQueue<TimeSpan>();

    var failedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();
    var passedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();

    var tasks = locationApiCalls.Select(async locationApiCall =>
    {
        await semaphoreSlim.WaitAsync();

        if (Interlocked.Increment(ref total) > 100)
        {
            completionTimes.TryDequeue(out var earliest);
            var elapsed = stopwatch.Elapsed - earliest;
            var delay = TimeSpan.FromSeconds(60) - elapsed;
            if (delay > TimeSpan.Zero)
                await Task.Delay(delay);
        }

        try
        {
            var response = await RestApi.GetResponseAsync(locationApi);
            if (response.IsSuccessful)
            {
                passedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
            else
            {
                failedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
        }
        finally
        {
            completionTimes.Enqueue(stopwatch.Elapsed);
            semaphoreSlim.Release();
        }
    });

    await Task.WhenAll(tasks);
    var passedResponsesList = passedResponses.SelectMany(x => x).ToList();
}
异步任务GetAllResponses(列表位置Apicalls) { var semaphoreSlim=新的信号量lim(initialCount:100,maxCount:100); var合计=0; var stopwatch=stopwatch.StartNew(); var completionTimes=新的ConcurrentQueue(); var failedResponses=新的ConcurrentBag(); var passedResponses=new ConcurrentBag(); var tasks=locationApiCalls.Select(异步locationApiCalls=> { 等待信号量lim.WaitAsync(); 如果(联锁增量(参考总数)>100) { completionTimes.TryDequeue(最早退出变量); var已用=秒表已用-最早; var delay=从秒(60)开始的时间跨度-已用时间; 如果(延迟>时间跨度为零) 等待任务。延迟(延迟); } 尝试 { var response=await RestApi.GetResponseAsync(locationApi); if(response.issucessful) { passedResponses.Add((IReadOnlyCollection)响应); } 其他的 { failedResponses.Add((IReadOnlyCollection)响应); } } 最后 { completionTimes.Enqueue(秒表运行时间); semaphoreSlim.Release(); } }); 等待任务。何时(任务); var passedResponsesList=passedResponses.SelectMany(x=>x.ToList(); }
如果您是从WinForms或WPF应用程序的UI线程调用此方法,请记住将
ConfigureAwait(false)
添加到其
await
语句中。

将查询限制为100并发与将查询限制为100每分钟不同。如果没有合适的方法,我看不到调试代码的方法,但即使有可能解决未完成或失败的任务,您仍然会遇到一个问题,即您似乎没有正确实施基于时间的限制。您的应用程序一次运行多少个实例?这是一个控制台应用程序吗?Web app?@mjwills,这是一个控制台应用程序。它一次只运行一个实例。您可以使用答案中的
RateLimiter
类。附带说明,
ConcurrentBag
不适合此用法。
ConcurrentQueue
会更好地为您服务,因为它保留了插入项目的顺序。
ConcurrentBag
是一个类。它不是线程安全的
列表
。将查询限制为100并发与将查询限制为100每分钟不同。如果没有合适的方法,我看不到调试代码的方法,但即使有可能解决未完成或失败的任务,您仍然会遇到一个问题,即您似乎没有正确实施基于时间的限制。您的应用程序一次运行多少个实例?这是一个控制台应用程序吗?Web app?@mjwills,这是一个控制台应用程序。它一次只运行一个实例。您可以使用答案中的
RateLimiter
类。附带说明,
ConcurrentBag
不适合此用法。
ConcurrentQueue<