HttpClient和HttpClientHandler必须被丢弃吗?

System.Net.Http.HttpClient和System.Net.Http.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接口。 我在正确的道路上?

他的回答是:

一般来说,这是正确的,虽然你必须小心“使用”和异步,因为他们不真正混合使用.NET 4,在.Net 4.5中,你可以使用“使用”语句中的“等待”。

顺便说一句,你可以重复使用相同的HttpClient多次[因为你喜欢,所以通常你不会创建/处置它们所有的时间。

第二个段落对这个问题来说太过分了,它并不关心你可以使用一个HttpClient实例多少次,但是如果它在你不再需要的时候有必要处理的话。

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

所以我的问题是:

  1. 鉴于当前的实现(.NET Framework 4.5),是否有必要在HttpClient和HttpClientHandler实例上调用Dispose()? 澄清:“必要的”我的意思是说,如果不处理任何消极后果,如资源泄漏或数据腐败风险。

  2. 如果没有必要,无论如何,它会是一个“良好的做法”,因为它们实现了IDisposable?

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

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

  5. 如果他们要求,或者如果这是一个推荐的做法,微软的例子是误导还是不安全?

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

许多亲密参与工作的人都表示了这一点。

请参阅Darrel Miller的博客文章和相关的SO帖子: HttpClient抓取内存泄漏的结果供参考。

我还强烈建议您阅读使用ASP.NET设计可演化的Web API的HttpClient一章,以了解引擎背后发生的情况,特别是此处引用的“生命周期”部分:

尽管HttpClient间接实现了IDisposable接口,但HttpClient的标准用法并不是在每次请求后处理它。 只要您的应用程序需要发出HTTP请求,HttpClient对象就会生存下来。 跨多个请求存在一个对象可以让您设置DefaultRequestHeaders的位置,并防止您必须在每个请求上重新指定CredentialCache和CookieContainer之类的内容,正如HttpWebRequest所需的那样。

甚至打开DotPeek。

根据我的理解,调用Dispose()只有在稍后锁定需要的资源(如特定的连接)时才是必需的。 总是建议您释放不再使用的资源,即使您不再需要这些资源,仅仅是因为您通常不应该占用您不使用的资源(双关语意)。

微软的例子是不正确的,必然。 所有使用的资源将在应用程序退出时释放。 而就这个例子来说,在HttpClient完成使用后,几乎立即发生了这种情况。 在类似的情况下,显式调用Dispose()有点多余。

但是,一般来说,当一个类实现了IDisposable ,理解就是,只要你已经准备好并且能够完成,你就应该Dispose()它的实例。 我假设这在HttpClient情况下尤其如此,其中没有明确记录资源或连接是否被保持/打开。 在连接将会被重新使用的情况下,你会想放弃它的连接 – 在这种情况下你并不是“完全准备好”的。

另请参阅: IDisposable.Dispose方法和何时调用Dispose

目前的答案有点混乱和误导,他们错过了一些重要的DNS影响。 我会尽量总结事情的清楚。

  1. 一般来说,大多数IDisposable对象理想情况下应该在处理 IDisposable 它们之后进行处理 ,尤其是那些拥有命名/共享OS资源的对象 。 HttpClient也不例外,因为Darrel Miller指出它分配取消令牌,请求/响应主体可以是非托管流。
  2. 但是, HttpClient的最佳做法是,应该创建一个实例并尽可能多地使用它(在多线程场景中使用它的线程安全成员 )。 因此,在大多数情况下, 你永远不会处理它,只是因为你会一直需要它
  3. 重复使用相同的HttpClient“永远”的问题是底层HTTP连接可能会保持打开对原始的DNS解析的IP,无论DNS更改 。 这可能是蓝色/绿色部署和基于DNS的故障转移等情况中的问题 。 处理这个问题有多种方法,最可靠的方法是在发生DNS更改后,服务器发送Connection:close头。 另一种可能性是在客户端循环使用HttpClient ,或者定期地或者通过一些了解DNS变化的机制。 有关更多信息,请参阅https://github.com/dotnet/corefx/issues/11224 (我建议您在使用链接的博客文章中建议的代码之前仔细阅读它)。

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

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

在Azure工作者角色中,在重复调用此方法(不处理HttpClient)之后,它最终将以SocketException (连接尝试失败)失败。

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

在典型的使用情况下(响应<2GB),不需要配置HttpResponseMessages。

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

  • 如果您正在将数据读取到byte [](例如GetByteArrayAsync)或字符串中,则所有数据都将被读取,因此不需要进行处理。
  • 其他重载将默认读取流最多2GB(HttpCompletionOption是ResponseContentRead,HttpClient.MaxResponseContentBufferSize默认为2GB)

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

无论您在HttpClient上调用Dispose取决于您是否要取消待处理的请求。

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

HttpClientHandler.cs – 处置

 ServicePointManager.CloseConnectionGroups(this.connectionGroupName); 

如果您不调用处理,则由计时器运行的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,而让空闲的连接计时器为你启动并关闭连接,但是如果你在using语句中调用dispose会更好。你知道你已经完成了一个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;