问题描述
我有 10-150 个长期存在的类对象,它们调用使用 HttpClient 执行简单 HTTPS API 调用的方法.PUT 调用示例:
I have anywhere from 10-150 long living class objects that call methods performing simple HTTPS API calls using HttpClient. Example of a PUT call:
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.UseCookies = true;
handler.CookieContainer = _Cookies;
using (HttpClient client = new HttpClient(handler, true))
{
client.Timeout = new TimeSpan(0, 0, (int)(SettingsData.Values.ProxyTimeout * 1.5));
client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Statics.UserAgent);
try
{
using (StringContent sData = new StringContent(data, Encoding.UTF8, contentType))
using (HttpResponseMessage response = await client.PutAsync(url, sData))
{
using (var content = response.Content)
{
ret = await content.ReadAsStringAsync();
}
}
}
catch (ThreadAbortException)
{
throw;
}
catch (Exception ex)
{
LastErrorText = ex.Message;
}
}
}
运行这些方法 2-3 小时后,包括通过 using
语句正确处理,该程序已蔓延到 1GB-1.5GB 内存,并最终因各种内存不足错误而崩溃.很多时候连接是通过不可靠的代理进行的,因此连接可能无法按预期完成(超时和其他错误很常见).
After 2-3 hours of running these methods, which include proper disposal via using
statements, the program has creeped to 1GB-1.5GB of memory and eventually crashes with various out of memory errors. Many times the connections are through unreliable proxies, so the connections may not complete as expected (timeouts and other errors are common).
.NET Memory Profiler 已经指出 HttpClientHandler
是这里的主要问题,指出它同时具有具有直接委托根的已处置实例"(红色感叹号)和已被处置但仍在仍然不是 GCed'(黄色感叹号).探查器指出的委托是 AsyncCallback
s,源自 HttpWebRequest.
.NET Memory Profiler has indicated that HttpClientHandler
is the main issue here, stating it has both 'Disposed instances with direct delegate roots' (red exclamation mark) and 'Instances that have been disposed but are still not GCed' (yellow exclamation mark). The delegates that the profiler indicates have been rooted are AsyncCallback
s, stemming from HttpWebRequest.
它也可能与 RemoteCertValidationCallback
有关,与 HTTPS 证书验证有关,因为 TlsStream
是根中更下方的对象,它是已处理但未 GCed"'.
It may also relate to RemoteCertValidationCallback
, something to do with HTTPS cert validation, as the TlsStream
is an object further down in the root that is 'Disposed but not GCed'.
考虑到所有这些 - 我怎样才能更正确地使用 HttpClient 并避免这些内存问题?我应该每小时强制一次 GC.Collect()
吗?我知道这被认为是不好的做法,但我不知道还有什么方法可以回收这些没有被妥善处理的内存,而且对于这些短期对象的更好的使用模式对我来说并不明显,因为它似乎是 .NET 对象本身的缺陷.
With all this in mind - how can I more correctly use HttpClient and avoid these memory issues? Should I force a GC.Collect()
every hour or so? I know that is considered bad practice but I don't know how else to reclaim this memory that isn't quite properly being disposed of, and a better usage pattern for these short-lived objects isn't apparent to me as it seems to be a flaw in the .NET objects themselves.
更新强制 GC.Collect()
没有效果.
进程的总托管字节数最多保持在 20-30 MB 左右,而进程总内存(在任务管理器中)继续攀升,表明存在非托管内存泄漏.因此,这种使用模式造成了非托管内存泄漏.
Total managed bytes for the process remain consistent around 20-30 MB at most while the process overall memory (in Task Manager) continues to climb, indicating an unmanaged memory leak. Thus this usage pattern is creating an unmanaged memory leak.
我已尝试根据建议创建 HttpClient 和 HttpClientHandler 的类级实例,但这并没有明显的效果.即使我将这些设置为类级别,由于代理设置经常需要更改,它们仍然会重新创建并且很少重新使用.HttpClientHandler 不允许在发起请求后修改代理设置或任何属性,因此我不断地重新创建处理程序,就像最初使用独立的 using
语句所做的那样.
I have tried creating class level instances of both HttpClient and HttpClientHandler per the suggestion, but this has had no appreciable effect. Even when I set these to class level, they are still re-created and seldom re-used due to the fact that the proxy settings often require changing. HttpClientHandler does not allow modification of proxy settings or any properties once a request has been initiated, so I am constantly re-creating the handler, just as was originally done with the independent using
statements.
HttpClienthandler 仍然使用直接委托根"处理 AsyncCallback -> HttpWebRequest.我开始怀疑 HttpClient 是否不是为快速请求和短期对象而设计的.看不到尽头..希望有人提出建议,使 HttpClientHandler 的使用可行.
HttpClienthandler is still being disposed with "direct delegate roots" to AsyncCallback -> HttpWebRequest. I'm starting to wonder if maybe the HttpClient just wasn't designed for fast requests and short-living objects. No end in sight.. hoping someone has a suggestion to make the use of HttpClientHandler viable.
内存分析器截图:
推荐答案
使用 Alexandr Nikitin 的重现形式,我发现这似乎仅在您将 HttpClient 设为一个短暂的对象时才会发生.如果您使处理程序和客户端长期存在,这似乎不会发生:
Using the repro form Alexandr Nikitin, I was able to discover that this seems to happen ONLY when you have HttpClient be a short lived object. If you make the handler and client long lived this does not seem to happen:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HttpClientMemoryLeak
{
using System.Net;
using System.Threading;
class Program
{
static HttpClientHandler handler = new HttpClientHandler();
private static HttpClient client = new HttpClient(handler);
public static async Task TestMethod()
{
try
{
using (var response = await client.PutAsync("http://localhost/any/url", null))
{
}
}
catch
{
}
}
static void Main(string[] args)
{
for (int i = 0; i < 1000000; i++)
{
Thread.Sleep(10);
TestMethod();
}
Console.WriteLine("Finished!");
Console.ReadKey();
}
}
}
这篇关于HttpClientHandler/HttpClient 内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!