【发布时间】:2021-09-28 11:20:31
【问题描述】:
我正在使用System.Threading.Tasks.Parallel.ForEach 并行同步运行一个方法。在方法结束时,它需要发出几十个 HTTP POST 请求,这些请求相互不依赖。由于我使用的是 .NET Framework 4.6.2,System.Net.Http.HttpClient 完全是异步的,所以我使用Nito.AsyncEx.AsyncContext 来避免死锁,格式如下:
public static void MakeMultipleRequests(IEnumerable<MyClass> enumerable)
{
AsyncContext.Run(async () => await Task.WhenAll(enumerable.Select(async c =>
await getResultsFor(c).ConfigureAwait(false))));
}
getResultsFor(MyClass c) 方法然后创建一个HttpRequestMessage 并使用以下方式发送它:
await httpClient.SendAsync(request);
然后解析响应并在 MyClass 的实例上设置相关字段。
我的理解是同步线程会阻塞在AsyncContext.Run(...),而一些任务是由AsyncContext拥有的单个AsyncContextThread异步执行的。当它们都完成后,同步线程将解除阻塞。
这适用于几百个请求,但是当它在五分钟内扩展到几千个时,一些请求开始从服务器返回 HTTP 408 Request Timeout 错误。我的日志表明这些超时发生在峰值负载时,发送的请求最多,并且在收到许多其他请求很久之后才发生超时。
我认为问题在于任务是awaitHttpClient 内的服务器握手,但它们没有按 FIFO 顺序继续,所以当它们继续时,握手已经过期。但是,除了使用System.Threading.SemaphoreSlim 强制一次只能执行一个任务await httpClient.SendAsync(...) 之外,我想不出任何方法来处理这个问题。
我的应用程序非常大,将其完全转换为异步是不可行的。
【问题讨论】:
-
不只是黑白。一次限制到 1 几乎会破坏您的并行方法。但是需要某种限制。您现在所做的基本上是 DoS 攻击。
-
该错误具体是 客户端 403 超时,而不是服务器端 503 错误。在高峰期,我在五分钟内发出几千个请求,这完全在服务器容量范围内。我有单独的错误处理代码供客户端处理服务器端问题。
-
是的,你可以 DoS 你自己的(本地)网络堆栈:) 解决方案是一样的:驯服你的马。也许在开始之前构建批次或给出一些抖动偏移时间......或者只是确保同时打开的请求少于 X 个。不过,理想情况下应该是什么 X 可能取决于客户端系统。
-
您可能想阅读以下内容:makolyte.com/…
-
@Fildor “确保同时打开的请求少于 X 个” - 除非我误解了某些东西,否则我认为这不能解决我的问题(除非 X == 1) .假设有 2 个插槽;什么是阻止请求#2 到 #200 通过插槽 B 而请求 #1 位于插槽 A 中等待继续,所以当它恢复时它立即超时?
标签: c# asynchronous async-await dotnet-httpclient