HttpClient查询偶尔会挂起



我这样初始化HttpClient

public static CookieContainer cookieContainer = new CookieContainer();
public static HttpClient httpClient = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, CookieContainer = cookieContainer }) { Timeout = TimeSpan.FromSeconds(120) };

因此如果在120秒内没有接收到响应,则所有查询都应该抛出CCD_ 2。但有些查询(如100000到100000中的1个)无限期地挂起。

我写了以下代码:

public static async Task<HttpResponse> DownloadAsync2(HttpRequestMessage httpRequestMessage)
{
HttpResponse response = new HttpResponse { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Response = "Timeout????????" };
Task task;
if (await Task.WhenAny(
task = Task.Run(async () =>
{
try
{
HttpResponseMessage r = await Global.httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);
response = new HttpResponse { Success = true, StatusCode = (int)r.StatusCode, Response = await r.Content.ReadAsStringAsync().ConfigureAwait(false) };
}
catch (TaskCanceledException)
{
response = new HttpResponse { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Response = "Timeout" };
}
catch (Exception ex)
{
response = new HttpResponse { Success = false, StatusCode = -1, Response = ex.Message + ": " + ex.InnerException };
}
}),
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(150)).ConfigureAwait(false);
})
).ConfigureAwait(false) != task)
{
Log("150 seconds passed");
}
return response;
}

其实际上偶尔执行CCD_ 3。

我这样称呼它:

HttpResponse r = await DownloadAsync2(new HttpRequestMessage
{
RequestUri = new Uri("https://address.com"),
Method = HttpMethod.Get
}).ConfigureAwait(false);

为什么TaskCanceledException有时在120秒后没有抛出?

我不知道你调用DownloadAsync2的频率有多高,但你的代码闻起来有很多爆裂和饥饿的ThreadPool的味道。

默认情况下,线程池中的初始线程数限制为CPU逻辑核的数量(对于今天的正常系统,通常为12个),如果线程池中线程不可用,则每个新线程的生成时间为500ms。

例如:

for (int i = 0; i < 1000; i++)
{
HttpResponse r = await DownloadAsync2(new HttpRequestMessage
{
RequestUri = new Uri("https://address.com"),
Method = HttpMethod.Get
}).ConfigureAwait(false);
}

此代码很有可能会被冻结,特别是如果您的代码中有一些lock或任何cpu intensive任务。因为您每次调用DownloadAsync2都会调用新线程,所以ThreadPool的所有线程都已消耗,而且还需要更多线程。

我知道也许你会说">我的所有任务都在等待中,它们发布用于其他作品";。但它们也用于启动新的CCD_ 9线程,并且在完成CCD_。

所以方法必须等到一个线程可用或生成后才能完成(甚至在超时之后)。罕见但可行。

在Windows上。NET,到同一端点的并发传出HTTP请求的数量限制为2(根据HTTP 1.1规范)。如果您创建大量并发请求到同一个端点,它们将排队。这是对你所经历的一种可能的解释。

另一种可能的解释是:您没有显式地设置HttpClient的Timeout属性,因此它默认为100秒。如果你不断提出新的请求,而之前的请求没有完成,系统资源就会用完。

我建议将Timeout属性设置为一个较低的值,与您进行的呼叫频率成比例(1秒?),并可以选择使用ServicePointManager.DefaultConnectionLimit 增加连接传出连接的数量

我发现是httpClient.SendAsync方法偶尔会挂起。因此,我添加了一个取消令牌,设置为X秒。但是,即使使用了消除器令牌,它有时也可能保持阻塞状态,并且永远不会抛出TaskCanceledException

因此,我采取了一种变通方法,使SendAsync任务永远停留在后台,并继续进行其他工作。

这是我的解决方法:

public static async Task<Response> DownloadAsync3(HttpRequestMessage httpRequestMessage, string caller)
{
Response response;
try
{
using CancellationTokenSource timeoutCTS = new CancellationTokenSource(httpTimeoutSec * 1000);
using HttpResponseMessage r = await Global.httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead, timeoutCTS.Token).WithCancellation(timeoutCTS.Token).ConfigureAwait(false);
response = new Response { Success = true, StatusCode = (int)r.StatusCode, Message = await r.Content.ReadAsStringAsync().ConfigureAwait(false) };
}
catch (TaskCanceledException)
{
response = new Response { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Message = "Timeout" };
}
catch (Exception ex)
{
response = new Response { Success = false, StatusCode = -1, Message = ex.Message + ": " + ex.InnerException };
}
httpRequestMessage.Dispose();
return response;
}
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}

使用Flurl,您可以为每个客户端、每个请求或全局配置Timeout。


// call once at application startup
FlurlHttp.Configure(settings => settings.Timeout = TimeSpan.FromSeconds(120));
string url = "https://address.com";
// high level scenario
var response = await url.GetAsync();
// low level scenario
await url.SendAsync(
HttpMethod.Get, // Example
httpContent, // optional
cancellationToken,  // optional
HttpCompletionOption.ResponseHeaderRead);  // optional
// Timeout at request level
await url
.WithTimeout(TimeSpan.FromSeconds(120))
.GetAsync();

流畅的HTTP文档

Flurl配置文档

答案是:

ThreadPool.SetMinThreads(MAX_THREAD_COUNT, MAX_THREAD_COUNT);

其中MAX_THREAD_COUNT是某个数字(我使用200)。您必须至少设置第二个参数(completionPortThreads),并且很可能设置第一个参数(workerThreads)。我已经设定了第一盘,但没有设定第二盘,现在它正在发挥作用,我将保持两盘。

唉,这不是答案。参见下方的评论

好吧,我正式放弃!我将代码替换为:

try
{
return Task.Run(() => httpClient.SendAsync(requestMessage)).Result;
}
catch (AggregateException e)
{
if (e.InnerException != null)
throw e.InnerException;
throw;
}

最新更新