简体   繁体   English

是否可以在 .NET 6.0 中限制 Parallel.ForEachAsync 以避免速率限制?

[英]Is it possible to throttle Parallel.ForEachAsync in .NET 6.0 to avoid rate limiting?

I'm fairly new to programming (< 3 years exp), so I don't have a great understanding of the subjects in this post.我对编程很陌生(< 3 年经验),所以我对这篇文章中的主题不太了解。 Please bear with me.请多多包涵。

My team is developing an integration with a third party system, and one of the third party's endpoints lacks a meaningful way to get a list of entities matching a condition.我的团队正在开发与第三方系统的集成,而第三方端点之一缺乏有意义的方式来获取与条件匹配的实体列表。

We have been fetching these entities by looping over the collection of requests, and adding the results of each awaited call to a list.我们一直在通过遍历请求集合来获取这些实体,并将每个等待调用的结果添加到列表中。 This works just fine, but getting the entities takes a lot longer than getting entities from other endpoints that lets us get a list of entities by providing a list of ids.这工作得很好,但是获取实体比从其他端点获取实体需要更长的时间,这让我们可以通过提供 id 列表来获取实体列表。

.NET 6.0 introduced Parallel.ForEachAsync() , which lets us execute multiple awaitable tasks asynchronously in parallel. .NET 6.0 引入了Parallel.ForEachAsync() ,它让我们可以并行异步执行多个等待任务。

For example:例如:

public async Task<List<TEntity>> GetEntitiesInParallelAsync<TEntity>(List<IRestRequest> requests) 
where TEntity : IEntity
{
    var entities = new ConcurrentBag<TEntity>();

    // Create a function that takes a RestRequest and returns the 
    // result of the request's execution, for each request
    var requestExecutionTasks = requests.Select(i => 
        new Func<Task<TEntity>>(() => GetAsync<TEntity>(i)));

    // Execute each of the functions asynchronously in parallel, 
    // and add the results to the aggregate as they come in
    await Parallel.ForEachAsync(requestExecutionTasks, new ParallelOptions
    {
        // This lets us limit the number of threads to use. -1 is unlimited
        MaxDegreeOfParallelism = -1 
    }, async (func, _) => entities.Add(await func()));

    return entities.ToList();
}

Using this code rather than the simple foreach-loop sped up the time it takes to get the ~30 entities on my test instance, by 91% on average.使用此代码而不是简单的 foreach 循环可以加快在我的测试实例上获取约 30 个实体所需的时间,平均缩短了 91%。 That's awesome.棒极了。 However, we are worried about the rate limiting that is likely to occur when we use it on a client's system with possibly thousands of entities.但是,我们担心在可能有数千个实体的客户端系统上使用它时可能会出现速率限制。 We have a system in place that detects the "you are rate limited"-message from their API, and cues the requests for a second or so before trying again, but this is not as much a good solution as it is a safety measure.我们有一个系统可以从他们的 API 中检测到“您的速率受限”消息,并在重试之前提示请求一秒钟左右,但这并不是一个好的解决方案,因为它是一种安全措施。

If we where just looping over the requests, we could have throttled the calls by doing something like await Task.Delay(minimumDelay) in each iteration of the loop.如果我们只是循环请求,我们可以通过在循环的每次迭代中执行类似于await Task.Delay(minimumDelay)的操作来限制调用。 Correct me if I'm wrong, but from what I understand this wouldn't actually work when executing the requests in parallel foreach, as it would make all requests wait the same amount of time before the execution.如果我错了,请纠正我,但据我了解,这在并行 foreach 执行请求时实际上不起作用,因为它会使所有请求在执行前等待相同的时间。 Is there a way to make each individual request wait a certain amount of time before execution, only if we are close to being rate limited?有没有办法让每个单独的请求在执行前等待一定的时间,只有当我们接近速率限制时? If at all possible, I would like to do this without limiting the number of threads to use.如果可能的话,我想在不限制要使用的线程数的情况下这样做。

My suggestion is to ditch the Parallel.ForEachAsync approach, and use instead the new Chunk LINQ operator in combination with the Task.WhenAll method.我的建议是放弃Parallel.ForEachAsync方法,而使用新的Chunk LINQ 运算符与Task.WhenAll方法结合使用。 You can launch 100 asynchronous operations every second like this:您可以像这样每秒启动 100 个异步操作:

public async Task<List<TEntity>> GetEntitiesInParallelAsync<TEntity>(
    List<IRestRequest> requests) where TEntity : IEntity
{
    var tasks = new List<Task<TEntity>>();
    foreach (var chunk in requests.Chunk(100))
    {
        tasks.AddRange(chunk.Select(request => GetAsync<TEntity>(request)));
        await Task.Delay(TimeSpan.FromSeconds(1.0));
    }
    return (await Task.WhenAll(tasks)).ToList();
}

It is assumed that the time required to launch an asynchronous operation (to invoke the GetAsync method) is negligible.假定启动异步操作(调用GetAsync方法)所需的时间可以忽略不计。

This approach has the inherent disadvantage that in case of an exception, the failure will not be propagated before all operations are completed.这种方法有一个固有的缺点,即在发生异常的情况下,在所有操作完成之前不会传播失败。 For comparison the Parallel.ForEachAsync method stops invoking the async delegate and completes ASAP, after the first failure is detected.为了比较, Parallel.ForEachAsync方法在检测到第一个故障后停止调用异步委托并尽快完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 .NET 6 Parallel.ForEachAsync 中需要两个令牌吗? - The need for two tokens in .NET 6 Parallel.ForEachAsync? 停止 Parallel.ForEachAsync - Stop Parallel.ForEachAsync 使用 Parallel.ForEachAsync - Using Parallel.ForEachAsync Parallel.ForEachAsync 不等待所有任务 - Parallel.ForEachAsync is not waiting for all tasks Parallel.ForEachAsync 任务与 ValueTask - Parallel.ForEachAsync Task vs ValueTask 如何使用 NoBuffering 运行 Parallel.ForEachAsync 循环? - How to run a Parallel.ForEachAsync loop with NoBuffering? Parallel.ForEachAsync 的实际最大并发任务数 - Actual maximum concurrent tasks of Parallel.ForEachAsync 如何打破 Parallel.ForEachAsync 循环,而不是取消它? - How to break the Parallel.ForEachAsync loop, not cancel it? Parallel.ForEachAsync 是普通 for 循环的替代品 + 附加到任务列表 + WhenAll 还是 Task.Run()? - Is Parallel.ForEachAsync a replacement to a plain for loop + append to task list + WhenAll OR Task.Run()? 来自 IEnumerable <task<t> &gt; 到 IAsyncEnumerable<t> 通过 yield 在 Parallel.ForEach/Parallel.ForEachAsync 中返回会给出错误 CS1621 </t></task<t> - From IEnumerable<Task<T>> to IAsyncEnumerable<T> by yield returning inside a Parallel.ForEach/Parallel.ForEachAsync gives error CS1621
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM