System.Net.Http.HttpClient和System.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所示。)
所以我的问题是:
在当前实现(.NET Framework 4.5)下,是否有必要在HttpClient和HttpClientHandler实例上调用Dispose()?澄清:“必要”是指如果不处置会产生任何负面影响,例如资源泄漏或数据腐败风险。
如果没有必要,那么它是否是一种“好习惯”,因为它们实现了IDisposable?
如果有必要(或推荐),上面提到this code是否安全地实现了它(对于.NET Framework 4.5)?
如果这些类不需要调用Dispose(),为什么它们被实现为IDisposable?
如果他们需要,或者如果是推荐的做法,那么微软的例子会误导或不安全吗?
答案 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影响。我会试着总结清楚的事情。
IDisposable
个对象应该在您完成后处理,特别是那些own Named/shared OS resources。 HttpClient
也不例外,因为Darrel Miller指出它分配取消令牌,而请求/响应正文可以是非托管流。Connection:close
标头。另一种可能性涉及在客户端循环HttpClient
,定期或通过一些了解DNS更改的机制。有关详细信息,请参阅https://github.com/dotnet/corefx/issues/11224(我建议在盲目使用链接博客文章中建议的代码之前仔细阅读。)答案 2 :(得分:16)
根据我的理解,只有在以后需要锁定资源(如特定连接)时才需要调用Dispose()
。它总是推荐来释放您不再使用的资源,即使您再也不需要它们,只是因为您通常不应该一般 &lt; em> 持有您不使用的资源(双关语)。
微软的例子不一定是不正确的。应用程序退出时将释放所有使用的资源。在该示例的情况下,这几乎在HttpClient
完成使用之后立即发生。在类似情况下,明确调用Dispose()
有点多余。
但是,一般来说,当一个类实现IDisposable
时,理解的是,只要您完全准备好并且能够完成,就应该Dispose()
。我认为在HttpClient
这样的情况下尤其如此,其中没有明确记录资源或连接是否被保持/打开。如果连接将在[很快]再次重复使用,您将要放弃Dipose()
它 - 您没有完全准备好&#34;在那种情况下。
答案 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”。
长答案:以下陈述同时适用:
IDisposable
个对象。他们并没有彼此之间的冲突。这只是一个如何组织代码以重用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在被垃圾回收之前可以关闭。
如果将HttpCompletionOption设置为ResponseHeadersRead或响应大于2GB,则应清理。这可以通过调用HttpResponseMessage上的Dispose或通过调用从HttpResonseMessage内容获得的Stream上的Dispose / Close或完全读取内容来完成。
是否在HttpClient上调用Dispose取决于您是否要取消挂起的请求。
答案 7 :(得分:2)
如果要处置HttpClient,可以将其设置为资源池。在应用程序结束时,您将处置资源池。
代码:
{{1}}
var handler = HttpClientHander.GetHttpClientHandle(new Uri(“base url”))。
答案 8 :(得分:1)
在构造函数中使用依赖注入可以更轻松地管理HttpClient
的生命周期 - 将生命周期管理带到需要它的代码之外,并使其在以后易于更改。
我目前的偏好是创建一个单独的http客户端类,每个目标端点域继承一次HttpClient
,然后使用依赖注入使其成为单例。 public class ExampleHttpClient : HttpClient { ... }
然后,我在需要访问该API的服务类中对自定义http客户端采用构造函数依赖。这解决了生命周期问题,并且在连接池方面具有优势。
的相关答案中查看有用的示例答案 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;