专栏名称: dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,Linux 主流平台
目录
相关文章推荐
泉州市场监管  ·  泉州小吃制作技艺地方标准正式发布 ·  3 天前  
51好读  ›  专栏  ›  dotNET跨平台

如何在并发中给 HttpClient 设置不同的超时时间?

dotNET跨平台  · 公众号  ·  · 2022-01-18 08:00

正文

咨询区

  • boot4life

为了能够复用 HttpClient 所打开的TCP链接,我不得不让所有的request共享一个单例,但问题来了,如果我简单的在每一次request之前修改 timeout 属性的话,会存在下面两个问题。

  1. 线程不安全。

  2. 并发时可能会错用到其他 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 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<stringGetStringAsync(string? requestUri, CancellationToken cancellationToken);
public Task PutAsync(Uri? requestUri, HttpContent content, CancellationToken cancellationToken);
public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);