咨询区
为了能够复用 HttpClient
所打开的TCP链接,我不得不让所有的request共享一个单例,但问题来了,如果我简单的在每一次request之前修改 timeout
属性的话,会存在下面两个问题。
并发时可能会错用到其他 request 的超时配置。
请问我该如何正确的处理?
回答区
你说的这种场景理论上是不可以设置 Timeout
属性,但不要忘了,你可以用 .NET 库中提供的取消机制类 CancellationTokenSource
,你可以在每一次 request 中带入 token 即可,然后用 CancelAfter()
方法来实现 Timeout 的功能,参考如下代码:
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
await httpClient.GetAsync("http://www.google.com", cts.Token);
}
哦,对了, HttpClient 的默认超时时间是 100s
,如果你在 CancelAfter
中设置了过大的超时时间,本质上来说并没有效果,为了解决这个 Max 的问题,我建议你给 HttpClient 设置一个无限期的超时,参考代码如下:
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
await httpClient.GetAsync("http://www.google.com", cts.Token);
}
楼上的大佬说的非常好,但在我的场景下是这么实现的,首先我有一个默认的 CancellationToken
,它可被用户手工干预,另外还有一个 CancellationToken
,它是为了模拟 timeout 的 CancelAfter
,然后通过 CancellationTokenSource.CreateLinkedTokenSource
来将两者结合起来,就可以做到既可以被 timeout 结束也可以被用户手工干预停止,下面是我的案例代码:
public async static Task SendRequest(CancellationToken cancellationToken)
{
var ctsForTimeout = new CancellationTokenSource();
ctsForTimeout.CancelAfter(TimeSpan.FromSeconds(5));
var cancellationTokenForTimeout = ctsForTimeout.Token;
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cancellationTokenForTimeout))
{
try
{
return await httpClient.GetAsync("http://asdfadsf", linkedCts.Token);
}
catch
{
//just for illustration purposes
if (cancellationTokenForTimeout.IsCancellationRequested)
{
Console.WriteLine("timeout");
}
else if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("other cancellation token cancelled");
}
throw;
}
}
}
点评区
说实话,用 CancellationToken
来替代 Timeout
机制确实是一个非常好的想法,如果大家细心的话,会发现 Framework 中很多涉及IO的操作都实现了这种 取消机制
,值得大家好好研究学习,比如下面代码:
public Task DeleteAsync(Uri? requestUri, CancellationToken cancellationToken);
public Task<byte[]> GetByteArrayAsync(string? requestUri, CancellationToken cancellationToken);
public Task GetStreamAsync(Uri? requestUri, CancellationToken cancellationToken);
public Task<string> GetStringAsync(string? requestUri, CancellationToken cancellationToken);
public Task PutAsync(Uri? requestUri, HttpContent content, CancellationToken cancellationToken);
public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);