HttpClient 查询偶尔挂起

Luk*_*741 5 c# timeout httpclient async-await

我这样初始化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) };
Run Code Online (Sandbox Code Playgroud)

所以TaskCanceledException如果在 120 秒内没有收到响应,所有查询都应该抛出。但是某些查询(例如 1 of 100 000-1 000 000)会无限挂起。

我写了以下代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

实际上偶尔会执行Log("150 seconds passed");.

我这样称呼它:

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

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

Arm*_*our 3

我不知道你调用的频率是多少DownloadAsync2,但你的代码闻起来很像线程池的爆裂和饥饿

默认情况下,ThreadPool 中的初始线程数量仅限于 CPU 逻辑核心的数量(对于当今的普通系统,通常为 12 个),如果 ThreadPool 中的线程不可用,则生成每个新线程需要 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);
}
Run Code Online (Sandbox Code Playgroud)

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

我知道也许你会说“我的所有任务都已等待,并且它们已发布用于其他作品”。但它们也会消耗用于启动新DownloadAsync2线程,并且您将达到这样的程度:完成后await Global.httpClient.SendAsync没有剩余线程用于重新分配和完成任务。

因此,方法必须等到一个线程可用或生成才能完成(即使在超时后)。罕见但可行。