可以重用 HttpClient,但每个请求可以使用不同的超时设置吗?

62

为了重用 HttpClient 中的开放式TCP连接,您需要为所有请求共享单个实例。

这意味着我们不能简单地使用不同的设置(例如超时或标头)来实例化 HttpClient

那么我们如何同时共享连接并使用不同的设置呢?事实上,旧的 HttpWebRequestWebClient 基础结构默认情况下就非常容易做到这一点。

请注意,仅在进行请求之前设置 HttpClient.Timeout 不具有线程安全性,并且在并发应用程序(例如 ASP.NET 网站)中无法工作。


1
除了“Timeout”之外,您想在同一实例上更改哪些设置?像头文件这样的东西可以根据请求进行设置,而变化的“BaseAddress”并没有太多意义,因为每个不同的地址都需要建立一个新的连接。实际上,“Timeout”可能是您想要在同一实例上变化的唯一属性。如果这回答了您的问题,我可以向您展示如何在每个请求中使用不同的超时时间。 - Todd Menier
@ToddMenier 那是一个很好的观点。那将回答这个问题,是的! - boot4life
{btsdaf} - Todd Menier
@ToddMenier 我非常确定这不是真的。这也意味着在同一个 HttpClient 对象上的并行请求不会同时执行,我也非常确定这不是情况。 - boot4life
4个回答

105

在幕后,HttpClient只是使用取消令牌来实现超时行为。如果您想针对每个请求进行变化,可以直接执行相同操作:

using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
await httpClient.GetAsync("http://www.google.com", cts.Token);

请注意,HttpClient 的默认超时时间为100秒,即使您在请求级别设置了更高的值,该请求也将在那一点被取消。为解决此问题,请在 HttpClient 上设置“max”超时时间,这可以是无限大:

httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;

6
正如另一个回答中提到的那样,这只能减少取消的时间。因此,如果您打算使用此方法,最好将默认超时设置为无限(或其他您满意的时间),并计划为每个请求提供自己的取消令牌。 - crush
@Todd Menier:我看了您为类似问题提供的答案,但我无法解决它,也许您可以帮助我: https://dev59.com/8LHma4cB1Zd3GeqPJlGt - Legion

13

接受的答案很好,但我想为将来寻找此问题解答的任何人提供另一种场景。在我的情况下,我已经使用了CancellationTokenSource,当用户选择取消时会取消其标记。因此,在这种情况下,您仍然可以使用CancellationTokenSource的CreateLinkedTokenSource方法来使用此技术。因此,在我的情况下,http操作将通过超时或用户干预来取消。以下是一个示例:

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 purposes
            if (cancellationTokenForTimeout.IsCancellationRequested)
            {
                Console.WriteLine("timeout");
            }
            else if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("other cancellation token cancelled");
            }
            throw;
        }
    }
}

1

有什么原因会更喜欢这个答案而不是被接受的答案吗?(只是想决定使用哪种方法) - jazza1000
1
@jazza1000(1)不创建链接令牌(heavywait)(2)更短的代码 - Alex from Jitbit
1
我担心HttpClient的“默认超时”仍然会在默认的100秒后取消操作,即使您在WaitAsync()中设置了5分钟。 - Fabio

0

如果您已经在使用CancellationToken,可能是从异步API端点中获取的,您可以使用CancellationTokenSource.CreateLinkedTokenSource将其与另一个CancellationToken组合。

以下是一个示例:

public async Task<HttpResponseMessage> SendRequestWithTimeout(HttpRequestMessage request, Timespan timeout, CancellationToken ct){
    var timeoutCt = new CancellationTokenSource(timeout).Token;
    var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCt).Token;
    return await httpClient.SendAsync(request, linkedCts);
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接