咨询区
- boot4life 
为了能够复用 HttpClient 所打开的TCP链接,我不得不让所有的request共享一个单例,但问题来了,如果我简单的在每一次request之前修改 timeout 属性的话,会存在下面两个问题。
- 线程不安全。 
- 并发时可能会错用到其他 request 的超时配置。 
请问我该如何正确的处理?
回答区
- Todd Menier 
你说的这种场景理论上是不可以设置 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);}- Brad Albright 
楼上的大佬说的非常好,但在我的场景下是这么实现的,首先我有一个默认的 CancellationToken ,它可被用户手工干预,另外还有一个 CancellationToken,它是为了模拟 timeout 的 CancelAfter ,然后通过 CancellationTokenSource.CreateLinkedTokenSource 来将两者结合起来,就可以做到既可以被 timeout 结束也可以被用户手工干预停止,下面是我的案例代码:
public async static Task<HttpResponseMessage> 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 purposesif (cancellationTokenForTimeout.IsCancellationRequested){Console.WriteLine("timeout");}else if (cancellationToken.IsCancellationRequested){Console.WriteLine("other cancellation token cancelled");}throw;}}
}点评区
说实话,用 CancellationToken 来替代 Timeout 机制确实是一个非常好的想法,如果大家细心的话,会发现 Framework 中很多涉及IO的操作都实现了这种 取消机制,值得大家好好研究学习,比如下面代码:
public Task<HttpResponseMessage> DeleteAsync(Uri? requestUri, CancellationToken cancellationToken);
public Task<byte[]> GetByteArrayAsync(string? requestUri, CancellationToken cancellationToken);
public Task<Stream> GetStreamAsync(Uri? requestUri, CancellationToken cancellationToken);
public Task<string> GetStringAsync(string? requestUri, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PutAsync(Uri? requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);