我像这样初始化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秒内未收到响应,则所有查询都应抛出TaskCanceledException
。
但是某些查询(例如100 000-1 000 000中的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;
}
实际上偶尔执行Log("150 seconds passed");
。
我这样称呼它:
HttpResponse r = await DownloadAsync2(new HttpRequestMessage
{
RequestUri = new Uri("https://address.com"),
Method = HttpMethod.Get
}).ConfigureAwait(false);
为什么TaskCanceledException
有时在120秒后仍未抛出?
答案 0 :(得分:2)
我不知道您叫DownloadAsync2
的频率,但是您的代码在爆裂和使ThreadPool饿死时闻起来很多。
默认情况下,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);
}
此代码很有可能会被冻结,特别是如果您在代码中的某个地方有某些lock
或任何cpu intensive
任务时。因为您在每次调用DownloadAsync2
时都调用了新线程,所以ThreadPool的所有线程都被消耗了,还需要更多线程。
我知道您也许会说:“ 我所有的任务都在等待中,并且可以用于其他工作”。但是它们也消耗了启动新的DownloadAsync2
线程的时间,并且您将得出结论,在完成await Global.httpClient.SendAsync
之后,没有线程可用于重新分配和完成任务。
所以方法必须等到一个线程可用或生成完成(即使在超时之后)。稀有但可行。
答案 1 :(得分:1)
我发现这是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);
}
答案 2 :(得分:1)
使用Flurl,您可以配置每个客户端,每个请求或全局的超时。
// 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();
答案 3 :(得分:1)
在Windows和.NET上,到同一终结点的并发传出HTTP请求的数量限制为2(根据HTTP 1.1规范)。如果您向同一端点创建大量并发请求,则它们将排队。那是对您所经历的一种可能的解释。
另一个可能的解释是:您没有显式设置HttpClient的Timeout属性,因此默认值为100秒。如果您继续发出新请求,而先前的请求没有完成,则系统资源将被耗尽。
我建议将Timeout属性设置为一个较低的值-与您拨打电话的频率成正比(1秒?),并可以选择使用ServicePointManager.DefaultConnectionLimit
增加并发传出连接的数量。答案 4 :(得分:0)
答案是:
ThreadPool.SetMinThreads(MAX_THREAD_COUNT, MAX_THREAD_COUNT);
其中MAX_THREAD_COUNT是一些数字(我用200)。您必须至少设置第二个参数(completionPortThreads),并且很可能设置第一个参数(workerThreads)。我已经设置了第一个,但没有设置第二个,现在它可以正常工作了,我将两个设置都保持不变。
A,这不是答案。查看下面的评论
答案 5 :(得分:0)
好的,我正式放弃!我将代码替换为:
try
{
return Task.Run(() => httpClient.SendAsync(requestMessage)).Result;
}
catch (AggregateException e)
{
if (e.InnerException != null)
throw e.InnerException;
throw;
}