【问题标题】:.Net Async Task restrict calls/second.Net 异步任务限制调用/秒
【发布时间】:2019-04-12 02:11:18
【问题描述】:

因此,我刚刚被要求将我的 .Net Async 代码对我们正在调用的特定 API 的调用次数限制为 5/秒。

我们的异步调用示例

var mylist = new List<Task<ApplicantDetails>>();

foreach (JToken result in results)
{
    mylist.Add(getCandidateResumeAsync(result));
}
await Task.WhenAll(mylist);

以及对getCandidateResumeAsync的API insode的实际调用

var candiateAttachmentsResponse = await Client.GetAsync(model["candidate"]["links"]["self"] + "/attachments");

我只需要每秒执行 5 次 getCandidateResumeAsync 函数。

有什么想法可以解决这个问题吗?

【问题讨论】:

  • 实现这一目标的方法有很多很多。您是在寻找快速而肮脏的东西,还是可重复使用和可调整的东西?这是代码中唯一需要进行这种限制的地方,还是需要以这种方式限制对该服务操作(或者可能是该服务或任何服务)的所有调用?
  • 我对该 API 进行了多次调用,并且所有调用都必须限制为 5/秒。快速和肮脏是可以接受的,因为目前我们的产品在我们实施之前处于离线状态。

标签: c# .net asp.net-mvc asynchronous


【解决方案1】:

我会使用信号量来跟踪当前正在运行的 API 调用数量,然后在每次调用完成时释放一个。像这样的:

public class Throttle
{
    private readonly TimeSpan perPeriod = TimeSpan.FromSeconds(1);
    private readonly SemaphoreSlim actionSemaphore = new SemaphoreSlim(5, 5);

    public async Task Queue(Func<Task> action, CancellationToken cancel)
    {
        await actionSemaphore.WaitAsync(cancel);
        try
        {
            await action();
        }
        finally
        {
            await Task.Delay(perPeriod, cancel).ContinueWith(_ => actionSemaphore.Release(1), cancel);
        }

    }
}

perPeriod 将设置为 1 秒,而 actionSemaphore 将设置为 5,这意味着它将允许 5 个请求一次运行。你可以这样称呼它:

Throttle t = new Throttle();
t.Queue(SomeAction, CancellationToken.None);

然后您可以等待所有任务完成。

【讨论】:

  • 如果action 可能很慢(或有很多时间变化),您可能希望在调用它之前计算您的“发布时间”,然后通过比较发布时间和当前时间。
【解决方案2】:

在这种情况下,您可以使用并行任务的限制,而不是限制每秒的任务数。

  static async Task  Main(string[] args) {

      const ushort maxConcurrentTasks = 5;

      var resultBag = new ConcurrentBag<ApplicantDetails>();
      var queue = new ConcurrentQueue<JToken>(items);

      var tasks = Enumerable.Range(0, maxConcurrentTasks)
        .Select(_ => GetResultAsync(queue, resultBag));

      await Task.WhenAll(tasks);

      var result = resultBag.ToArray();
    }

    private static async Task GetResultAsync(ConcurrentQueue<JToken> queue, ConcurrentBag<ApplicantDetails> resultBag) {
      while (queue.TryDequeue(out var queueItem)) {
        var result = await getCandidateResumeAsync(queueItem);
        resultBag.Add(result);
      }
    }

我知道这不是您要找的。但也许这种方法你也可以接受。

【讨论】:

  • 这是“任何时候最多 5 个”而不是“每秒最多 5 个”。可能前者是 OP 实际需要的,但他们声明的要求是后者。
【解决方案3】:

感谢大家的建议。

只是添加这个以防将来对其他人有帮助。

这是我想出的,它似乎工作正常。

        int counter = 0;
        foreach (JToken result in results)
        {
            counter++;
            if ((counter % 5) == 0) Task.Delay(1000);
            mylist.Add(getCandidateResumeAsync(result));
        }

【讨论】:

  • Task.Delay 返回一个任务。如果您没有对该任务执行任何操作,那么调用它几乎毫无意义(当然,实际的调用开销仍然会影响您,以及安排在适当时间完成任务的机制仍然被调用)。这意味着你可以取出这个柜台的东西,它几乎是一样的。这段代码并没有像你想象的那样做。
  • @Damien_The_Unbeliever 最后,API 提供者改变了主意,要求我将调用限制为 10 个并发线程,以便不再使用此代码。我很想看看您为 5/秒的要求建议的解决方案,以供将来参考。
  • 我喜欢Mike's answer 的要求,可以根据我的评论进行修改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多