system.net.httpclient和system.net.httpclienthandler在。net Framework 4.5中实现了IDisposable(通过System.Net.Http.HttpMessageInvoker)。

using语句文档说:

作为规则,当使用IDisposable对象时,应该声明和 在using语句中实例化它。

这个答案使用了这个模式:

var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("foo", "bar"),
        new KeyValuePair<string, string>("baz", "bazinga"),
    });
    cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
    var result = client.PostAsync("/test", content).Result;
    result.EnsureSuccessStatusCode();
}

但是来自Microsoft的最明显的例子既没有显式调用也没有隐式调用Dispose()。例如:

最初的博客文章宣布HttpClient的发布。 HttpClient的实际MSDN文档。 BingTranslateSample GoogleMapsSample WorldBankSample

在公告的评论中,有人问微软员工:

在检查了你的样品后,我发现你没有进行处理 对HttpClient实例的操作。我已经使用了HttpClient的所有实例 在我的应用程序上使用声明,我认为这是正确的方式 因为HttpClient实现了IDisposable接口。我是在 正确的道路?

他的回答是:

一般来说,这是正确的,尽管你必须小心 "using"和async,因为它们在。net 4和。net 4.5中不能真正混合 可以在“using”语句中使用“await”。 顺便说一句,你可以重复使用同一个HttpClient,次数不限 通常情况下,您不会一直创建/处理它们。

第二段对这个问题来说是多余的,它不关心您可以使用HttpClient实例多少次,而是关心在您不再需要它之后是否有必要处理它。

(更新:事实上,第二段是答案的关键,如下文由@DPeden提供。)

所以我的问题是:

Is it necessary, given the current implementation (.NET Framework 4.5), to call Dispose() on HttpClient and HttpClientHandler instances? Clarification: by "necessary" I mean if there are any negative consequences for not disposing, such as resource leakage or data corruption risks. If it's not necessary, would it be a "good practice" anyway, since they implement IDisposable? If it's necessary (or recommended), is this code mentioned above implementing it safely (for .NET Framework 4.5)? If these classes don't require calling Dispose(), why were they implemented as IDisposable? If they require, or if it's a recommended practice, are the Microsoft examples misleading or unsafe?


在我的理解中,只有当Dispose()锁定稍后需要的资源(如特定连接)时才有必要调用Dispose()。总是建议释放你不再使用的资源,即使你不再需要它们,只是因为你通常不应该持有你不使用的资源。

微软的例子不一定是错误的。当应用程序退出时,将释放所使用的所有资源。在那个例子中,它几乎是在HttpClient被使用完之后立即发生的。在类似的情况下,显式调用Dispose()有点多余。

但是,一般来说,当一个类实现IDisposable时,只要您完全准备好并能够执行,您就应该Dispose()它的实例。我认为在像HttpClient这样的情况下尤其如此,其中没有明确地记录资源或连接是否被保持/打开。在连接将[很快]再次被重用的情况下,您将希望放弃它的dispose()处理——在这种情况下,您还没有“完全准备好”。

参见: IDisposable。Dispose方法和何时调用Dispose


一般的共识是您不(不应该)需要处理HttpClient。

许多密切参与其工作方式的人都说过这一点。

请参阅Darrel Miller的博客文章和相关的SO文章:HttpClient爬行导致内存泄漏。

我还强烈建议你阅读《用ASP设计可进化的Web api》中的HttpClient章节。NET提供关于底层发生了什么的上下文,特别是这里引用的“生命周期”部分:

Although HttpClient does indirectly implement the IDisposable interface, the standard usage of HttpClient is not to dispose of it after every request. The HttpClient object is intended to live for as long as your application needs to make HTTP requests. Having an object exist across multiple requests enables a place for setting DefaultRequestHeaders and prevents you from having to re-specify things like CredentialCache and CookieContainer on every request as was necessary with HttpWebRequest.

甚至可以打开DotPeek。


在我的例子中,我在一个实际执行服务调用的方法中创建了一个HttpClient。喜欢的东西:

public void DoServiceCall() {
  var client = new HttpClient();
  await client.PostAsync();
}

在Azure工作人员角色中,在反复调用该方法之后(没有处理HttpClient),它最终会以SocketException失败(连接尝试失败)。

我使HttpClient成为一个实例变量(在类级别上处理它),这个问题就消失了。所以我会说,是的,释放HttpClient,假设它是安全的(你没有未完成的异步调用)。


我认为应该使用单例模式来避免创建HttpClient实例并一直关闭它。如果你使用的是。net 4.0,你可以使用下面的示例代码。有关单例模式检查的更多信息,请点击这里。

class HttpClientSingletonWrapper : HttpClient
{
    private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 

    public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}

    private HttpClientSingletonWrapper()
    {
    }
}

使用下面的代码。

var client = HttpClientSingletonWrapper.Instance;

在典型使用中(响应<2GB),没有必要Dispose HttpResponseMessages。

如果HttpClient方法的流内容没有被完全读取,那么它们的返回类型应该被丢弃。否则,CLR没有办法知道这些流可以被关闭,直到它们被垃圾回收。

如果你正在将数据读入字节[](例如GetByteArrayAsync)或字符串,所有的数据都被读取,所以不需要处理。 其他重载将默认读取流到2GB (HttpCompletionOption是ResponseContentRead, HttpClient。MaxResponseContentBufferSize默认为2GB)

如果你设置HttpCompletionOption为ResponseHeadersRead或者响应大于2GB,你应该清理。这可以通过在HttpResponseMessage上调用Dispose或在从HttpResonseMessage内容中获得的流上调用Dispose/Close来完成,也可以通过完全读取内容来完成。

是否在HttpClient上调用Dispose取决于是否想要取消挂起的请求。


目前的答案有点令人困惑和误导,它们遗漏了一些重要的DNS含义。我会试着把情况总结清楚。

Generally speaking most IDisposable objects should ideally be disposed when you are done with them, especially those that own Named/shared OS resources. HttpClient is no exception, since as Darrel Miller points out it allocates cancellation tokens, and request/response bodies can be unmanaged streams. However, the best practice for HttpClient says you should create one instance and reuse it as much as possible (using its thread-safe members in multi-threaded scenarios). Therefore, in most scenarios you'll never dispose of it simply because you will be needing it all the time. The problem with re-using the same HttpClient "forever" is that the underlying HTTP connection might remain open against the originally DNS-resolved IP, regardless of DNS changes. This can be an issue in scenarios like blue/green deployment and DNS-based failover. There are various approaches for dealing with this issue, the most reliable one involving the server sending out a Connection:close header after DNS changes take place. Another possibility involves recycling the HttpClient on the client side, either periodically or via some mechanism that learns about the DNS change. See https://github.com/dotnet/corefx/issues/11224 for more information (I suggest reading it carefully before blindly using the code suggested in the linked blog post).


Dispose()调用下面的代码,关闭HttpClient实例打开的连接。代码是通过使用dotPeek进行反编译创建的。

HttpClientHandler.cs -特性

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

如果你不调用dispose,那么ServicePointManager。由定时器运行的MaxServicePointIdleTime将关闭http连接。缺省值是100秒。

ServicePointManager.cs

internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback);
private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000);

private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
  ServicePoint servicePoint = (ServicePoint) context;
  if (Logging.On)
    Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode()));
  lock (ServicePointManager.s_ServicePointTable)
    ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString);
  servicePoint.ReleaseAllConnectionGroups();
}

如果你还没有将空闲时间设置为无限,那么不调用dispose并让空闲连接计时器启动并为你关闭连接似乎是安全的,尽管如果你知道你已经完成了HttpClient实例并更快地释放资源,那么在using语句中调用dispose会更好。


如果你想要释放HttpClient,你可以把它设置为一个资源池。在应用程序的末尾,分配资源池。

代码:

// Notice that IDisposable is not implemented here!
public interface HttpClientHandle
{
    HttpRequestHeaders DefaultRequestHeaders { get; }
    Uri BaseAddress { get; set; }
    // ...
    // All the other methods from peeking at HttpClient
}

public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
{
    public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
    public static HashSet<Uri> _uris;

    static HttpClientHander()
    {
        _httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
        _uris = new HashSet<Uri>();
        SetupGlobalPoolFinalizer();
    }

    private DateTime _delayFinalization = DateTime.MinValue;
    private bool _isDisposed = false;

    public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
    {
        HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
        _uris.Add(baseUrl);
        httpClient._delayFinalization = DateTime.MinValue;
        httpClient.BaseAddress = baseUrl;

        return httpClient;
    }

    void IDisposable.Dispose()
    {
        _isDisposed = true;
        GC.SuppressFinalize(this);

        base.Dispose();
    }

    ~HttpClientHander()
    {
        if (_delayFinalization == DateTime.MinValue)
            _delayFinalization = DateTime.UtcNow;
        if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
            GC.ReRegisterForFinalize(this);
    }

    private static void SetupGlobalPoolFinalizer()
    {
        AppDomain.CurrentDomain.ProcessExit +=
            (sender, eventArgs) => { FinalizeGlobalPool(); };
    }

    private static void FinalizeGlobalPool()
    {
        foreach (var key in _uris)
        {
            HttpClientHander value = null;
            if (_httpClientsPool.TryGetValue(key, out value))
                try { value.Dispose(); } catch { }
        }

        _uris.Clear();
        _httpClientsPool = null;
    }
}

var handler = HttpClientHander.GetHttpClientHandle(new Uri(“base url”)).

HttpClient作为一个接口,不能调用Dispose()。 Dispose()将被垃圾回收器以延迟的方式调用。 或者当程序通过析构函数清理对象时。 使用弱引用+延迟清理逻辑,因此只要它被频繁重用,它就会一直被使用。 它只为传递给它的每个基本URL分配一个新的HttpClient。原因由Ohad Schneider解释,答案如下。更改基本url时的不良行为。 HttpClientHandle允许在测试中进行mock


在构造函数中使用依赖注入使管理HttpClient的生命周期变得更容易——将生命周期管理置于需要它的代码之外,并使它在以后的日期中容易更改。

我目前的偏好是创建一个单独的http客户端类,每个目标端点域从HttpClient继承一次,然后使用依赖注入使其成为单例。公共类ExampleHttpClient: HttpClient{…}

然后,我在需要访问该API的服务类中的自定义http客户机上获取构造函数依赖项。这解决了生存期问题,并且在连接池方面具有优势。

您可以在https://stackoverflow.com/a/50238944/3140853的相关回答中看到一个工作示例


简短的回答:不,目前接受的答案中的陈述是不准确的:“普遍的共识是您不(不应该)需要处理HttpClient”。

长话短说:以下两种说法都是正确的,同时也是可以实现的:

引用自官方文档的“HttpClient旨在被实例化一次,并在应用程序的整个生命周期中被重用”。 一个IDisposable对象被假定/建议被释放。

而且它们彼此之间并不必然冲突。这只是你如何组织你的代码来重用HttpClient并且仍然正确地处理它的问题。

从我的另一个回答中引用了一个更长的回答:

见到人不是巧合 在一些博客文章中指责HttpClient的IDisposable接口 使他们倾向于使用using (var client = new HttpClient()){…}模式 然后导致套接字处理程序耗尽的问题。

我认为这可以归结为一个未说出口的(错误的)概念: “一个IDisposable对象预期是短命的”。

然而,当我们以这种风格编写代码时,它确实看起来像一个短命的东西:

using (var foo = new SomeDisposableObject())
{
    ...
}

IDisposable的官方文件 从来没有提到IDisposable对象必须是短命的。 根据定义,IDisposable只是一种允许您释放非托管资源的机制。 仅此而已。从这个意义上说,你最终会触发处置, 但它并不要求你在短时间内这样做。

因此,正确选择何时触发处置是你的工作, 基于真实对象的生命周期需求。 没有什么能阻止你长久地使用一次性卫生纸:

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // A really long loop

                // Or you may even somehow start a daemon here

            }

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

With this new understanding, now we revisit that blog post, we can clearly notice that the "fix" initializes HttpClient once but never dispose it, that is why we can see from its netstat output that, the connection remains at ESTABLISHED state which means it has NOT been properly closed. If it were closed, its state would be in TIME_WAIT instead. In practice, it is not a big deal to leak only one connection open after your entire program ends, and the blog poster still see a performance gain after the fix; but still, it is conceptually incorrect to blame IDisposable and choose to NOT dispose it.


因为似乎还没有人在这里提到它,在。net Core >=2.1和。net 5.0+中管理HttpClient和HttpClientHandler的最佳新方法是使用HttpClientFactory。

它以一种干净和易于使用的方式解决了上述大多数问题和陷阱。摘自Steve Gordon的博客文章:

将以下包添加到.Net Core(2.1.1或更高版本)项目中:

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

将此添加到Startup.cs:

services.AddHttpClient();

注射使用:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    }
}

探索Steve博客中的系列文章,了解更多功能。


不,不要在每个请求时都创建一个新的(即使你处理了旧的)。您将导致服务器本身(不仅仅是应用程序)因为操作系统上网络级别的端口耗尽而崩溃!

请阅读下面我对一个非常类似的问题的回答。很明显,您应该将HttpClient实例视为单例,并在请求之间重用。

在WebAPI客户端中每次调用创建一个新的HttpClient的开销是多少?