HttpClientFactory创建的HttpClient实例应该被销毁吗?

87

所以,我在我的Startup.cs中向服务集合注册了一个命名客户端:

services.AddHttpClient(someServiceName, 
                       client => client.BaseAddress = baseAddress);

现在,可以从我的服务提供者中注入一个 IHttpClientFactory

使用这个 IHttpClientFactory,我可以创建一个客户端实例:

var client = httpClientFactory.CreateClient(someServiceName)

从前,必须非常小心地处理HttpClient实例的处理,因为这通常不是正确的做法

但是,现在我们有了HttpClientFactory,这还重要吗?这个client可以毫不担心地被处理吗?例如:

using (var httpClient = httpClientFactory.CreateClient(someServiceName))
using (var response = await httpClient.PostAsync(somePath, someData))
{
    var content = await response.Content.ReadAsAsync<SomeResponse>();
    //...
}

请查看这篇文章,它解释了工厂如何处理客户端:https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore - Nkosi
@Nkosi 是的,我已经读过了。然而,我找到了一些代码看起来很像我问题中的最后一段,我在想是否需要修复它。 - spender
3个回答

60

调用Dispose方法并非必需,但如果有需要,您仍然可以调用它。

证明:HttpClient和生命周期管理

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

让我们来检查一下DefaultHttpClientFactory的源代码

public HttpClient CreateClient(string name)
{
    if (name == null)
    {
        throw new ArgumentNullException(nameof(name));
    }

    var handler = CreateHandler(name);
    var client = new HttpClient(handler, disposeHandler: false);

    var options = _optionsMonitor.Get(name);
    for (var i = 0; i < options.HttpClientActions.Count; i++)
    {
        options.HttpClientActions[i](client);
    }

    return client;
}
HttpMessageHandler的实例存储了HttpClient的非托管资源。在经典场景中,HttpClient创建HttpMessageHandler的实例,并在自身释放时同时释放它。
从上述代码可以看出,不同的HttpClient实例共享一个HttpMessageHandler实例,并且不会释放它(disposeHandler: false)。
因此,调用HttpClient.Dispose不会产生任何效果。但这并不危险。

42

不,你不应该丢弃你的客户端。更一般地说,你不应该丢弃通过依赖注入容器检索到的任何东西,在ASP.NET Core中,默认情况下是服务集合。生命周期由依赖注入容器管理,因此如果你丢弃了客户端,但它稍后被注入到某个地方,你将会得到一个ObjectDisposedException异常。让容器处理释放。

实际上,这是对于IDisposable类的常见误解。只有当你的类本身拥有依赖项时,才应该自己实现IDisposable。如果所有的依赖都是注入的,你就不应该实现IDisposable,因为它没有拥有需要释放的任何东西。同样,你不应该释放注入到你的类中的任何东西,因为它不拥有那些依赖项。只有释放你明确创建的东西。如果你看不到关键字new,你可能不应该释放。


69
有点儿令人困惑,因为客户端不是通过依赖注入容器检索的,而是工厂通过依赖注入容器检索的。 - Kieren Johnstone
15
但是,在httpClientFactory.CreateClient(someServiceName)中的命名似乎暗示了你要创建一个新的实例,所以这就是令人困惑的地方。我赞同@KierenJohnstone的观点。 - Wiebe Tijsma
6
资源是否被 new 出来并不重要,重要的是你是否控制它的生命周期。在工厂的情况下,是工厂进行实例化并控制其生命周期,而不是你自己。这就是关键。只释放属于你自己的资源。 - Chris Pratt
3
你同意这种混淆会不会减少一些,如果C#或.NET包括一些关于对象生命周期和所有权的语义,以防止这种混淆吗?甚至是一些简单的东西,比如一个接口(例如IDisposableHahahNotReally),或者通过智能指针类型的对象来引用对象, 比如 OwnedObject<T>.InstanceBorrowedObject<T>.Value - Dai
5
也许不需要这样做。我知道 HttpMessageHandler 是工厂实际持有的内容,而 HttpClient 实例是短暂作用域的。但是,简单的事实仍然存在:你不应该处置你没有创建或拥有的东西。如果实现发生变化,你可能会突然破坏某些东西。 - Chris Pratt
显示剩余6条评论

0
可能有点晚来参加派对,但我正在浏览这里的Microsoft学习资料,其中有一个代码示例引起了我的注意:
public sealed class TodoService
{
    private readonly IHttpClientFactory _httpClientFactory = null!;
    private readonly ILogger<TodoService> _logger = null!;

    public TodoService(
        IHttpClientFactory httpClientFactory,
        ILogger<TodoService> logger) =>
        (_httpClientFactory, _logger) = (httpClientFactory, logger);

    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        ////////////////////////////////////////////
        //                                        //
        // HERE IT IS - USING WITH THE HTTP CLIENT//
        //                                        //
        ////////////////////////////////////////////
        // Create the client
        using HttpClient client = _httpClientFactory.CreateClient();
    
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo types
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"https://jsonplaceholder.typicode.com/todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

        return todos ?? Array.Empty<Todo>();
    }
    catch (Exception ex)
    {
        _logger.LogError("Error getting something fun to say: {Error}", ex);
    }

    return Array.Empty<Todo>();
}

}

我对看到其他人的想法很感兴趣。

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