我应该缓存并重用通过HttpClientFactory创建的HttpClient吗?

34
我们可以在这里阅读到你正在错误地使用HttpClient,并且它正在破坏你的软件,我们不应该为每个http请求创建和释放HttpClient。相反,它应该被缓存和重用(例如作为DI容器中的单例)。同样,在官方的.NET文档中也提到了HttpClient

HttpClient应该在应用程序的整个生命周期中只被实例化一次并重复使用。在每个请求中实例化一个HttpClient类将在高负载下耗尽可用的套接字数量。这将导致SocketException错误。下面是一个正确使用HttpClient的示例。

建议使用HttpClientFactory,但在查看了之后,
  public interface IHttpClientFactory
  {
    /// <summary>
    /// Creates and configures an <see cref="T:System.Net.Http.HttpClient" /> instance using the configuration that corresponds
    /// to the logical name specified by <paramref name="name" />.
    /// </summary>
    /// <param name="name">The logical name of the client to create.</param>
    /// <returns>A new <see cref="T:System.Net.Http.HttpClient" /> instance.</returns>
    /// <remarks>
    /// <para>
    /// Each call to <see cref="M:System.Net.Http.IHttpClientFactory.CreateClient(System.String)" /> is guaranteed to return a new <see cref="T:System.Net.Http.HttpClient" />
    /// instance. Callers may cache the returned <see cref="T:System.Net.Http.HttpClient" /> instance indefinitely or surround
    /// its use in a <langword>using</langword> block to dispose it when desired.
    /// </para>
    /// <para>
    /// The default <see cref="T:System.Net.Http.IHttpClientFactory" /> implementation may cache the underlying
    /// <see cref="T:System.Net.Http.HttpMessageHandler" /> instances to improve performance.
    /// </para>
    /// <para>
    /// Callers are also free to mutate the returned <see cref="T:System.Net.Http.HttpClient" /> instance's public properties
    /// as desired.
    /// </para>
    /// </remarks>
    HttpClient CreateClient(string name);
  }

每次调用都会创建一个HttpClient实例,并且调用者可以缓存它。
每次调用IHttpClientFactory.CreateClient都保证返回一个新的HttpClient实例。调用者可以无限期地缓存返回的实例,或者在需要时将其包装在using块中以进行释放。
所以问题是,我应该完全依赖HttpClientFactory还是仍然应该缓存从中创建的HttpClient?
在我们的项目中,每次发出请求时我们都使用HttpClientFactory.CreateClient,但仍然会出现套接字异常。

文档说每次都会创建一个新的实例,而你知道应该使用单个实例……所以显然你应该缓存?我在这里漏掉了什么? - BradleyDotNET
1
@BradleyDotNET 好吧,有很多关于HttpClientFactory的文章,但没有人说过那个。请理解我的困惑。 - mardok
@mardok 不,那是 HttpClientFactory 的工作。 - Panagiotis Kanavos
@BradleyDotNET,是HttpClientFactory负责缓存和刷新实例。它的存在是为了让人们不必自己缓存。 - Panagiotis Kanavos
1
@mardok HttpClientFactory 缓存实际执行网络操作的 HttpMessageHandlers。 - Panagiotis Kanavos
2个回答

39

HttpClient只是因为它的HttpMessageHandlerIDisposable而成为IDisposable。实际上,应该是HttpMessageHandler应该是长期存在的。

HttpClientFactory通过在内部保持一个长期存在的HttpMessageHandler来工作。每当您请求一个HttpClient时,它使用长期存在的HttpMessageHandler,并告诉HttpClientHttpClient被释放时不要释放它。

您可以在GitHub上看到:

public HttpClient CreateClient(string name)
{
    // ...

    // Get a cached HttpMessageHandler
    var handler = CreateHandler(name);

    // Give it to a new HttpClient, and tell it not to dispose it
    var client = new HttpClient(handler, disposeHandler: false);

    // ...

    return client;
}

所以,从技术上讲,无论你是缓存HttpClient还是立即释放它都没有关系 - 释放它不会做任何事情(因为它被告知不要释放其HttpClientHandler,因为那是由HttpClientFactory管理的)。
关于释放HttpClientMSDN说

不需要释放客户端。释放会取消正在进行的请求,并确保在调用Dispose后无法再使用给定的HttpClient实例。IHttpClientFactory会跟踪和释放HttpClient实例使用的资源。通常可以将HttpClient实例视为不需要释放的.NET对象。

在引入IHttpClientFactory之前,保持单个HttpClient实例长时间存活是一种常见的模式。在迁移到IHttpClientFactory后,这种模式变得不再必要。

我怀疑你所看到的SocketException有不同的原因。也许你可以提一个关于它们的新问题?

(摊手)我正在调查一个用户不存在的错误,所以我的大脑已经变成了黑洞。 - Panagiotis Kanavos
@PanagiotisKanavos,我发现你在这里的评论中的推理方式与你在原始问题的评论中所说的相反,这让我感到有些奇怪!没关系,我会删除我的回复。 - canton7
disposing HttpClient doesn't do anything” 这似乎与问题中的文章实证不符(请参见此部分)。该文章通过 netstat 显示,避免使用 using 语句将 10 个调用和 10 个连接减少为 10 个调用和 1 个连接。using 语句会处理 HttpClient。因此,如果 处理 HttpClient 使用 1 个连接,而 处理 它使用 10 个连接... 那么你怎么能说 'disposing it doesn't do anything'? - undefined
@TylerH,文章中的代码没有使用HttpClientFactory - undefined
@n0rd 我明白了,所以canton7在这里的意思是“在使用HttpClientFactory时,处理它不会有任何作用”? - undefined
@TylerH 是的,这个问题和答案都是关于从 HttpClientFactory 获取的 HttpClients。 - undefined

6
在ASP.NET Core 2.2版本中,有一些很好的改变。现在期望使用HttpClient只能通过DI来消耗它,这将在内部使用HttpClientFactory为您处理所有必要的缓存。以下文档文章已更新以反映这些新用例:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2 此外,来自ASP.NET Core团队的@RyanNowak在以下ASP.Net Core社区站立会议上涵盖了所有这些更改:https://www.youtube.com/watch?v=Lb12ZtlyMPg。如果您还没有观看过,请强烈建议观看,因为它非常信息丰富和教育性。
以下是一个小示例,展示了用法。在Startup.ConfigureServices方法调用中:
services.AddHttpClient();

注意: 有多种使用模式,这是最基本的一个。请查看文档以获取其他可能更适合您需求的模式。

稍后,在您想要进行http请求的类中,依赖于IHttpClientFactory并让DI根据需要实例化它。以下是来自Microsoft Docs的示例:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/aspnet/docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

我在使用开放式套接字时遇到了类似的问题。您可以在 https://github.com/dotnet/corefx/issues/35698 中阅读我的症状。尽管 HttpMessageHandler 似乎被重用,但我仍然有许多新连接。我甚至对此有一个问题 https://dev59.com/5LLma4cB1Zd3GeqPXDI4 - Ahmet

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