Fer*_*eia 315 c# idisposable using .net-4.5 dotnet-httpclient
.NET Framework 4.5中的System.Net.Http.HttpClient和System.Net.Http.HttpClientHandler实现了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();
}
Run Code Online (Sandbox Code Playgroud)
但是微软最明显的例子并没有Dispose()明确地或隐含地调用.例如:
在公告的评论中,有人问微软员工:
检查完样本后,我发现您没有对HttpClient实例执行dispose操作.我用的HttpClient的所有实例使用我的应用程序发言,我认为这是正确的方式,因为HttpClient的实现IDisposable接口.我在正确的道路上吗?
他的回答是:
一般情况下这是正确的,尽管你必须小心"使用"和异步,因为它们并不真正混合.Net 4,在.Net 4.5中你可以在"using"语句中使用"await".
顺便说一下,你可以像你喜欢的那样重复使用相同的HttpClient,所以通常你不会一直创建/处理它们.
第二段对于这个问题是多余的,它不关心你可以使用HttpClient实例多少次,而是关注是否有必要在你不再需要它之后处理它.
(更新:事实上,第二段是答案的关键,如下面的@DPeden所示.)
所以我的问题是:
鉴于当前的实现(.NET Framework 4.5),是否有必要在HttpClient和HttpClientHandler实例上调用Dispose()?澄清:"必要"是指如果不处置会产生任何负面影响,例如资源泄漏或数据腐败风险.
如果没有必要,那么它是否是一个"好的做法",因为它们实现了IDisposable?
如果有必要(或推荐),上面提到的代码是否安全地实现了它(对于.NET Framework 4.5)?
如果这些类不需要调用Dispose(),为什么它们被实现为IDisposable?
如果他们需要,或者如果是推荐的做法,微软的例子是误导性的还是不安全的?
Dav*_*den 245
普遍的共识是你不(不应该)需要处理HttpClient.
许多与其工作方式密切相关的人都说过这一点.
请参阅Darrel Miller的博文和相关的SO帖子:HttpClient爬行导致内存泄漏以供参考.
我还强烈建议您阅读使用ASP.NET设计Evolvable Web API中的HttpClient章节,了解有关内幕的内容,特别是此处引用的"生命周期"部分:
尽管HttpClient间接实现了IDisposable接口,但HttpClient的标准用法并不是在每次请求后都将其处理掉.只要您的应用程序需要发出HTTP请求,HttpClient对象就会存在.在多个请求之间存在一个对象,可以设置一个用于设置DefaultRequestHeaders的位置,并防止您必须在HttpWebRequest所需的每个请求上重新指定CredentialCache和CookieContainer之类的内容.
甚至打开DotPeek.
Oha*_*der 41
目前的答案有点令人困惑和误导,他们错过了一些重要的DNS影响.我会试着总结清楚的事情.
IDisposable对象应该在完成后处理,特别是那些拥有命名/共享OS资源的对象.HttpClient也不例外,因为Darrel Miller指出它分配取消令牌,而请求/响应机构可以是非托管流.Connection:close在DNS发生更改后,服务器发送标头.另一种可能性涉及HttpClient在客户端循环,定期或通过一些了解DNS更改的机制.有关详细信息,请参阅https://github.com/dotnet/corefx/issues/11224(我建议在盲目使用链接博客文章中建议的代码之前仔细阅读).svi*_*gen 17
根据我的理解,Dispose()只有在以后锁定您需要的资源(如特定连接)时才需要调用.总是建议释放你不再使用的资源,即使你不再需要它们,只是因为你通常不应该持有你不使用的资源(双关语).
微软的例子不一定是不正确的.应用程序退出时将释放所有使用的资源.在该示例的情况下,这HttpClient在使用完成后几乎立即发生.在类似情况下,明确调用Dispose()有点多余.
但是,一般来说,当一个类实现时IDisposable,理解是你应该Dispose()在你完全准备好并且能够完成它的实例.我认为这种情况尤其如此,HttpClient其中没有明确记录资源或连接是否被保持/打开.在其中连接将被再次使用的情况下[快],你要放弃Dipose()它的ING -你不是在这种情况下,"完全准备好".
另请参阅: IDisposable.Dispose方法和何时调用Dispose
Dispose()调用下面的代码,它关闭HttpClient实例打开的连接.代码是通过dotPeek反编译创建的.
HttpClientHandler.cs - 处理
ServicePointManager.CloseConnectionGroups(this.connectionGroupName);
Run Code Online (Sandbox Code Playgroud)
如果您不调用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();
}
Run Code Online (Sandbox Code Playgroud)
如果你没有将空闲时间设置为无限,那么看起来安全不要调用dispose并让空闲连接计时器启动并为你关闭连接,尽管你最好在using语句中调用dispose.你知道你已经完成了一个HttpClient实例,并且可以更快地释放资源.
就我而言,我在一个实际进行服务调用的方法中创建了一个HttpClient.就像是:
public void DoServiceCall() {
var client = new HttpClient();
await client.PostAsync();
}
Run Code Online (Sandbox Code Playgroud)
在Azure辅助角色中,在重复调用此方法(不丢弃HttpClient)之后,它最终会失败SocketException(连接尝试失败).
我让HttpClient成为一个实例变量(在类级别处理它),问题就消失了.所以我会说,是的,处理HttpClient,假设它是安全的(你没有出色的异步调用)来这样做.
简短的回答:不,当前接受的答案中的陈述不准确:“一般的共识是您不需要(不应)处置HttpClient”。
长答案:以下两个陈述都是正确的,并且可以同时实现:
IDisposable假定/建议放置一个对象。而且他们彼此之间不必冲突。只是如何组织代码以重复使用HttpClientAND并正确处置它。
在某些博客文章中看到人们指责HttpClient' IDisposable接口如何使他们倾向于使用该using (var client = new HttpClient()) {...}模式,然后导致疲惫的套接字处理程序问题,这并不是巧合。
我认为可以归结为一个不言而喻的概念: “一个IDisposable对象应该是短暂的”。
但是,当我们以这种方式编写代码时,虽然看起来确实是一时的事:
using (var foo = new SomeDisposableObject())
{
...
}
Run Code Online (Sandbox Code Playgroud)
有关IDisposable的官方文档
从不提及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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
通过这种新的理解,现在我们重新访问该博客文章,我们可以清楚地注意到“修复”只初始化了HttpClient一次,但从未对其进行处理,这就是为什么我们从其netstat输出中可以看到,连接保持在ESTABLISHED状态,这意味着它已经没有正确关闭。如果关闭,则其状态将改为TIME_WAIT。实际上,在整个程序结束后只泄漏一个打开的连接并没什么大不了的,并且在修正后,博客发布者仍然可以看到性能的提高;但是,将IDisposable归咎于并选择不处置它在概念上是不正确的。
由于这里似乎还没有人提到它,因此在.Net Core 2.1中管理HttpClient和HttpClientHandler的最佳新方法是使用HttpClientFactory。
它以干净且易于使用的方式解决了上述大多数问题和陷阱。摘自史蒂夫·戈登的精彩博客文章:
将以下软件包添加到您的.Net Core(2.1.1或更高版本)项目中:
Microsoft.AspNetCore.All
Microsoft.Extensions.Http
Run Code Online (Sandbox Code Playgroud)
将此添加到Startup.cs:
services.AddHttpClient();
Run Code Online (Sandbox Code Playgroud)
注入和使用:
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
探索Steve博客中的一系列帖子,以获取更多功能。
| 归档时间: |
|
| 查看次数: |
115869 次 |
| 最近记录: |