我应该缓存和重用从HttpClientFactory创建的HttpClient吗?

mar*_*dok 7 c# httpclient dotnet-httpclient .net-core

我们可以在这里阅读到您使用的是HTTPCLIENT WRONG,它正在破坏您的软件,我们不应该为每个http请求创建和处置HttpClient。相反,应该对其进行缓存和重用(例如,作为DI容器中的Singleton)。在HttpClient的 .NET官方文档中也是如此:

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);
  }
Run Code Online (Sandbox Code Playgroud)

它说每个调用将始终创建一个HttpClient实例,调用者可以对其进行缓存。

确保每次对IHttpClientFactory.CreateClient的调用都返回一个新的HttpClient实例。调用方可以无限期缓存返回的实例,也可以在需要时将其使用包含在using块中以将其处置。

所以问题是我应该完全依靠HttpClientFactory还是应该仍然缓存从中创建的HttpClient

在我们的项目中,每次发出请求时,我们都使用HttpClientFactory.CreateClient,他仍然会有套接字异常。

can*_*on7 6

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

HttpClientFactory通过保持HttpMessageHandler内部长期存在来工作。每当您请求a时HttpClient,它都会使用寿命长的HttpMessageHander,并在处置a时告诉其HttpClient 不要对其进行处置HttpClient

您可以在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;
}
Run Code Online (Sandbox Code Playgroud)

因此,从技术上讲,您是否缓存HttpClient或立即处置它都没有关系-处置它不会做任何事情(因为已告知不要处置HttpClientHandler,因为这是由进行管理的HttpClientFactory)。

关于配置HttpClientMSDN说

不需要处理客户端。Disposal取消传出的请求,并确保在调用Dispose之后不能使用给定的HttpClient实例。IHttpClientFactory跟踪并处置HttpClient实例使用的资源。HttpClient实例通常可以视为不需要处理的.NET对象。

在IHttpClientFactory诞生之前,通常使用一种模式来使单个HttpClient实例长期存活。迁移到IHttpClientFactory后,此模式就不再需要。

我怀疑SocketException您看到的原因有不同。也许问一个针对他们的新问题?


Art*_*tak 6

在 ASP.NET Core 2.2 版本中,情况发生了很大的变化。HttpClient预计消耗的方式仅通过 DI,它在内部为您使用 .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();
Run Code Online (Sandbox Code Playgroud)

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

稍后,在课程中,您想要从何处发出 http 请求,获取依赖项IHttpClientFactory并让 DI 根据需要为您实例化它。以下是来自 Microsoft 文档的示例:

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>();
        }                               
    }
}
Run Code Online (Sandbox Code Playgroud)