Do HttpClient and HttpClientHandler have to be disposed?
system.net.http.httpclient和system.net.http.httpclienthandler在.NET Framework 4.5(通过实现IDisposable system.net.http.httpmessageinvoker)。
声明说:"
As a rule, when you use an IDisposable object, you should declare and
instantiate it in a using statement.
回答:这个模式使用本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } |
但最明显的例子是微软的Don’t
- 博客文章的原announcing of HttpClient的释放。
- 在MSDN文档的HttpClient的实际。
- bingtranslatesample
- googlemapssample
- worldbanksample
在这里的评论,有人问:"微软的员工
After checking your samples, I saw that you didn't perform the dispose
action on HttpClient instance. I have used all instances of HttpClient
with using statement on my app and I thought that it is the right way
since HttpClient implements the IDisposable interface. Am I on the
right path?
他的回答是:
In general that is correct although you have to be careful with
"using" and async as they dont' really mix in .Net 4, In .Net 4.5 you
can use"await" inside a"using" statement.Btw, you can reuse the same HttpClient as many times are [as] you like so
typically you won't create/dispose them all the time.
第二条本superfluous是需要担心的问题是,你可以使用多少次在HttpClient实例,但如果它是一个必要的信息后,你不再需要它。
(更新:在这样的事实,第二部分是关键的答案,下面是提供的dpeden red"。)
所以我的问题是:
这是没有必要的,目前的执行(.NET Framework 4.5),调用Dispose()在与httpclienthandler HttpClient的实例?clarification:"必要的"如果有任何消极后果的均值为disposing资源困境,如泄漏或数据腐败的风险。
如果是没有必要的,它是一个"良好做法",无论如何,因为他们实现IDisposable。
如果是必要的(或没有),这是实施IT安全码以上(for .NET Framework 4.5)?
如果论文类不需要调用Dispose(),为实现IDisposable为什么是他们?
如果他们需要,或如果它是一个伟大的例子是微软的做法,对于成本或不安全?
一般的共识是,您不需要(不应该)处理httpclient。
许多与它的工作方式密切相关的人都说了这一点。
请参阅darrel-miller的博客文章和相关的so文章:httpclient crawling results in memory leak以供参考。
我还强烈建议您阅读httpclient一章,从使用ASP.NET设计可进化的Web API中了解引擎盖下正在发生的事情,特别是这里引用的"生命周期"部分:
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。
目前的答案有点令人困惑和误导,它们缺少一些重要的DNS含义。我试着总结一下事情的发展方向。
据我所知,只有在锁定稍后需要的资源(如特定连接)时,才需要调用
微软的例子不一定是不正确的。当应用程序退出时,将释放所有使用的资源。在这个例子中,几乎在使用
但是,一般来说,当一个类实现
参见:IDisposable.Dispose方法和何时调用Dispose
dispose()调用下面的代码,关闭由httpclient实例打开的连接。代码是用dotpeek进行反编译创建的。
httpclienthandler.cs-释放
1 | ServicePointManager.CloseConnectionGroups(this.connectionGroupName); |
如果不调用Dispose,则由计时器运行的ServicePointManager.MaxServicePointIdleTime将关闭HTTP连接。默认值为100秒。
服务点管理器.cs
1 2 3 4 5 6 7 8 9 10 11 12 | 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。类似:
1 2 3 4 |
在一个Azure工作者角色中,在反复调用此方法(不处理httpclient)之后,它最终会在
我使httpclient成为一个实例变量(在类级别处理它),问题就消失了。所以我会说,是的,释放httpclient,假设它是安全的(您没有未完成的异步调用)。
简短回答:不,当前接受的回答中的陈述不准确:"一般的共识是您不需要(不应该)处置httpclient"。
长话短说:以下两种说法都是正确的,并且可以同时实现:
它们不一定会相互冲突。这只是如何组织代码以重用
我的另一个答案引用了一个更长的答案:
见到人不是巧合在一些博客文章中指责
我认为这归结为一个潜台词(mis?)概念:"IDisposable对象应该是短期的"。
然而,当我们以这种方式编写代码时,它看起来确实是一个短暂的东西:
1 2 3 4 |
可转让证件的正式文件从来没有提到过
因此,正确选择何时触发处置是您的工作,基于真实对象的生命周期需求。没有什么能阻止您长期使用IDisposable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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(); } } } |
有了这个新的理解,现在我们重新访问那个博客帖子,我们可以清楚地注意到"fix"将
在典型用法中(响应<2GB),不需要处理httpResponseMessages。
如果httpclient方法的流内容未完全读取,则应释放其返回类型。否则,在这些流被垃圾收集之前,CLR无法知道它们是否可以被关闭。
- 如果要将数据读取到byte[](例如getbytearrayasync)或字符串中,则会读取所有数据,因此无需进行释放。
- 其他重载将默认为读取高达2GB的流(httpCompletionOption为ResponseContentRead,httpClient.MaxResponseContentBufferSize默认为2GB)
如果将httpCompletionOption设置为ResponseHeadersRead或响应大于2GB,则应清除。这可以通过对httpResponseMessage调用Dispose或对从httpResponseMessage内容获取的流调用Dispose/Close或完全读取内容来实现。
是否在httpclient上调用dispose取决于是否要取消挂起的请求。
如果要释放httpclient,可以将其设置为资源池。在应用程序的末尾,您将处理您的资源池。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | // 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允许在测试中模拟
在构造器中使用依赖项注入可以更容易地管理
我当前的首选项是为每个目标端点域创建一个独立的HTTP客户端类,该类从
然后,我在需要访问该API的服务类中对自定义HTTP客户机采用构造函数依赖关系。这解决了生存期问题,并且在连接池方面具有优势。
您可以在https://stackoverflow.com/a/50238944/3140853的相关答案中看到一个工作示例。
我认为应该使用singleton模式来避免创建httpclient的实例并一直关闭它。如果您使用.NET 4.0,可以使用下面的示例代码。有关singleton模式的更多信息,请查看此处。
1 2 3 4 5 6 7 8 9 10 | class HttpClientSingletonWrapper : HttpClient { private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }} private HttpClientSingletonWrapper() { } } |
使用下面的代码。
1 | var client = HttpClientSingletonWrapper.Instance; |