是否必须处理HttpClient和HttpClientHandler?

时间:2013-03-29 14:17:57

标签: c# .net-4.5 idisposable using dotnet-httpclient

.NET Framework 4.5中的

System.Net.Http.HttpClientSystem.Net.Http.HttpClientHandler实现了IDisposable(通过System.Net.Http.HttpMessageInvoker)。

using声明文档说:

  

通常,当您使用IDisposable对象时,您应该声明和   在using语句中实例化它。

This answer使用此模式:

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();
}

但是微软最明显的例子并没有明确地或隐含地调用Dispose()。例如:

announcement的评论中,有人问微软员工:

  

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

他的回答是:

  

一般情况下这是正确的,尽管你必须小心   “使用”和异步,因为他们不真正混合.Net 4,在.Net 4.5你   可以在“使用”声明中使用“等待”。

     

顺便说一下,你可以重复使用相同的HttpClient,就像你喜欢的那样   通常你不会一直创造/处置它们。

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

(更新:事实上,第二段是答案的关键,如下面的@DPeden所示。)

所以我的问题是:

  1. 在当前实现(.NET Framework 4.5)下,是否有必要在HttpClient和HttpClientHandler实例上调用Dispose()?澄清:“必要”是指如果不处置会产生任何负面影响,例如资源泄漏或数据腐败风险。

  2. 如果没有必要,那么它是否是一种“好习惯”,因为它们实现了IDisposable?

  3. 如果有必要(或推荐),上面提到this code是否安全地实现了它(对于.NET Framework 4.5)?

  4. 如果这些类不需要调用Dispose(),为什么它们被实现为IDisposable?

  5. 如果他们需要,或者如果是推荐的做法,那么微软的例子会误导或不安全吗?

12 个答案:

答案 0 :(得分:225)

普遍的共识是你不(不应该)需要处理HttpClient。

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

请参阅Darrel Miller's blog post以及相关的SO帖子:HttpClient crawling results in memory leak以供参考。

我还强烈建议您阅读the HttpClient chapter from Designing Evolvable Web APIs with ASP.NET了解有关幕后内容的背景信息,特别是此处引用的“生命周期”部分:

  

虽然HttpClient间接实现了IDisposable   接口,HttpClient的标准用法是不处理它   在每次请求之后。 HttpClient对象旨在用作as   您的应用程序需要发出HTTP请求。有一个对象   跨多个请求存在一个地方进行设置   DefaultRequestHeaders并阻止您重新指定   每个请求都有像CredentialCache和CookieContainer这样的东西   是必要的HttpWebRequest。

甚至打开DotPeek。

答案 1 :(得分:37)

目前的答案有点令人困惑和误导,他们缺少一些重要的DNS影响。我会试着总结清楚的事情。

  1. 一般而言,理想情况下,大多数IDisposable个对象应该在您完成后处理,特别是那些own Named/shared OS resourcesHttpClient也不例外,因为Darrel Miller指出它分配取消令牌,而请求/响应正文可以是非托管流。
  2. 但是,best practice for HttpClient表示您应该创建一个实例并尽可能多地重用它(在多线程场景中使用其thread-safe members)。因此,在大多数情况下你永远不会因为你一直需要它而处理它
  3. “永远”重复使用相同的HttpClient的问题是the underlying HTTP connection might remain open against the originally DNS-resolved IP, regardless of DNS changes。在蓝/绿部署和基于DNS的故障转移等方案中,这可能是一个问题。处理此问题有多种方法,最可靠的方法是在DNS发生更改后,服务器发送Connection:close标头。另一种可能性涉及在客户端循环HttpClient,定期或通过一些了解DNS更改的机制。有关详细信息,请参阅https://github.com/dotnet/corefx/issues/11224(我建议在盲目使用链接博客文章中建议的代码之前仔细阅读。)

答案 2 :(得分:16)

根据我的理解,只有在以后需要锁定资源(如特定连接)时才需要调用Dispose()。它总是推荐来释放您不再使用的资源,即使您再也不需要它们,只是因为您通常不应该一般 &lt; em> 持有您不使用的资源(双关语)。

微软的例子不一定是不正确的。应用程序退出时将释放所有使用的资源。在该示例的情况下,这几乎在HttpClient完成使用之后立即发生。在类似情况下,明确调用Dispose()有点多余。

但是,一般来说,当一个类实现IDisposable时,理解的是,只要您完全准备好并且能够完成,就应该Dispose()。我认为在HttpClient这样的情况下尤其如此,其中没有明确记录资源或连接是否被保持/打开。如果连接将在[很快]再次重复使用,您将要放弃Dipose()它 - 您没有完全准备好&#34;在那种情况下。

另见: IDisposable.Dispose MethodWhen to call Dispose

答案 3 :(得分:8)

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

HttpClientHandler.cs - Dispose

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并让空闲连接计时器启动并为您关闭连接,尽管您最好调用dispose如果你知道你已经完成了HttpClient实例并且更快地释放资源,那么在using语句中。

答案 4 :(得分:5)

就我而言,我在一个实际进行服务调用的方法中创建了一个HttpClient。就像是:

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

在Azure辅助角色中,在重复调用此方法(不释放HttpClient)之后,它最终会因SocketException失败(连接尝试失败)。

我将HttpClient作为一个实例变量(在类级别处置它),问题就消失了。所以我会说,是的,处理HttpClient,假设它是安全的(你没有出色的异步调用)这样做。

答案 5 :(得分:4)

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

长答案:以下陈述同时适用:

  1. “HttpClient旨在实例化一次并在应用程序的整个生命周期中重复使用”,引自official documentation
  2. 假设/建议处置IDisposable个对象。
  3. 他们并没有彼此之间的冲突。这只是一个如何组织代码以重用HttpClient并仍然正确处理它的问题。

    我的another answer引用的更长的答案

    看到别人并不是巧合 在some blog posts中指责HttpClient的{​​{1}}接口 使他们倾向于使用IDisposable模式 然后导致用尽的套接字处理程序问题。

    我认为这归结为一个未说出口的(错误?)概念: "an IDisposable object is expected to be short-lived"

    但是,当我们用这种风格编写代码时,它看起来确实是一个短命的东西:

    using (var client = new HttpClient()) {...}

    official documentation on IDisposable 永远不会提到using (var foo = new SomeDisposableObject()) { ... } 对象必须是短暂的。 根据定义,IDisposable只是一种允许您释放非托管资源的机制。 而已。从这个意义上说,你预计会最终触发处置, 但它并不要求你以短暂的方式这样做。

    因此,您的工作是正确选择何时触发处置, 基于您的真实对象的生命周期要求。 没有什么可以阻止你以长期的方式使用IDisposable:

    IDisposable

    有了这种新的理解,现在我们重新审视that blog post, 我们可以清楚地注意到“修复”初始化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(); } } } 一次但从不处理它, 这就是为什么我们可以从它的netstat输出看到, 连接仍处于ESTABLISHED状态,这意味着它尚未正确关闭。 如果它被关闭,它的状态将改为TIME_WAIT。 实际上,在整个程序结束后只打开一个连接并不是什么大不了的事, 并且博客海报仍然看到修复后的性能提升; 但是,归咎于IDisposable并且选择不处理它在概念上是不正确的。

答案 6 :(得分:3)

在典型用法(响应&lt; 2GB)中,没有必要处理HttpResponseMessages。

如果HttpClient方法的流内容未完全读取,则应该将它们设置为Disposed。否则,CLR无法知道这些Streams在被垃圾回收之前可以关闭。

  • 如果您正在将数据读入byte [](例如GetByteArrayAsync)或字符串,则会读取所有数据,因此无需处置。
  • 其他重载将默认读取Stream高达2GB(HttpCompletionOption为ResponseContentRead,HttpClient.MaxResponseContentBufferSize默认为2GB)

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

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

答案 7 :(得分:2)

如果要处置HttpClient,可以将其设置为资源池。在应用程序结束时,您将处置资源池。

代码:

{{1}}

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

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

答案 8 :(得分:1)

在构造函数中使用依赖注入可以更轻松地管理HttpClient的生命周期 - 将生命周期管理带到需要它的代码之外,并使其在以后易于更改。

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

然后,我在需要访问该API的服务类中对自定义http客户端采用构造函数依赖。这解决了生命周期问题,并且在连接池方面具有优势。

您可以在https://stackoverflow.com/a/50238944/3140853

的相关答案中查看有用的示例

答案 9 :(得分:0)

由于似乎还没有人在这里提到它,因此在.Net Core 2.1中管理HttpClient和HttpClientHandler的最佳新方法是使用HttpClientFactory

它以干净且易于使用的方式解决了上述大多数问题和陷阱。来自Steve Gordon's great blog post

将以下软件包添加到.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博客中的一系列帖子,以获取更多功能。

答案 10 :(得分:0)

请仔细阅读我对以下类似问题的回答。应该清楚的是,您应该将HttpClient实例视为单例,并在请求中重复使用。

What is the overhead of creating a new HttpClient per call in a WebAPI client?

答案 11 :(得分:-3)

我认为应该使用单例模式来避免必须创建HttpClient的实例并一直关闭它。如果您使用的是.Net 4.0,则可以使用如下示例代码。有关单身模式检查的更多信息here

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;