c# .Net 大量使用HttpClient导致内存溢出异常

易刀锋 2020-02-11 04:29:30
接触c#半年左右,大部分都在做和HTTP协议相关的项目,但是苦于内存溢出问题导致很多项目的用户体验都不太好。

在模拟HTTP请求时,我一直都是使用HttpClient类库,因为这个库模拟Http请求实在是太方便。

当使用时间久了开始发现当项目中大量使用了HttpClient来做请求时,程序的运行内存就会不断增长,特别是在多线程使用中增长速度更加快,
当增长到一定量后就会抛出内存溢出异常。

查阅了大量资料也没有找到解决方法,偶然间又想起刚开始接触的时候用过的HttpWebRequest类库(后来弃用的原因就是用起来没有HttpClient方便),将项目中各种使用HttpClient类库的方法替换成使用HttpWebRequest后神奇发现内存居然就稳定了!!

!!发现这个问题后今天立刻来做了个测试对比两个类库的在使用时的内存增长。
首先准备两个方法分别测试两个类库,如下:



/// <summary>
/// HttpClient类库测试
/// </summary>
/// <param name="proxy"></param>
void HttpClientTest(string proxy) {
try {
using (HttpClientHandler handler = new HttpClientHandler()) {
if (!string.IsNullOrEmpty(proxy)) {//判断代码参数是否为空
handler.UseProxy = true;
handler.Proxy = new WebProxy(proxy);//不为空则设置代理
}

using (HttpClient http = new HttpClient(handler)) {

using (HttpResponseMessage response = http.GetAsync("https://www.baidu.com").Result) {//向服务端发送请求

Console.WriteLine($"HttpClient 状态码:{response.StatusCode}");
string result = response.Content.ReadAsStringAsync().Result;//读取响应内容
}
}
}
} catch (Exception e) {
Console.WriteLine($"HttpClientTest 异常消息:{e.Message}");
}
}
/// <summary>
/// HttpWebRequest类库测试
/// </summary>
/// <param name="proxy"></param>
void HttpWebRequestTest(string proxy) {
try {
HttpWebRequest http = WebRequest.CreateHttp("https://www.baidu.com");
http.Method = "GET";

if (!string.IsNullOrEmpty(proxy)) {
http.Proxy = new WebProxy(proxy);
}

http.KeepAlive = false;//只要不将KeepAlive设置为False,内部就会维护连接池

using (HttpWebResponse response = http.GetResponse() as HttpWebResponse) {
Console.WriteLine($"HttpWebRequest 状态码:{response.StatusCode}");

using (StreamReader reader = new StreamReader(response.GetResponseStream())) {//获取响应流
string result = reader.ReadToEnd();//读取响应内容
}
}
} catch (Exception ex) {
Console.WriteLine($"HttpWebRequest 异常消息:{ex.Message}");
}
}


两个方法分别测试请求https://www.baidu.com
再准备两个调用方法来进行调用


private void Button_HttpWebRequestTest_Click(object sender, EventArgs e) {
new Action(() => {
for (int i = 0; i < 2000; i++) {
HttpWebRequestTest("127.0.0.1:8888");//设置代理,使Fiddler能够看到协议流程
}
}).BeginInvoke(null, null);
}

private void Button_HttpClientTest_Click(object sender, EventArgs e) {
new Action(() => {
for (int i = 0; i < 2000; i++) {
HttpClientTest("127.0.0.1:8888");
}
}).BeginInvoke(null, null);
}


两个方法分别调用2000次测试方法,测试结果如下图:
首先是程序启动,未开始测试前,内存占用8MB左右:


HttpClient测试完成,占用内存137.7MB,而且随机调用次数越多,占用内存也会越高


HttpWebRequest测试到1000次时,占用内存29.7MB


HttpWebRequest测试完成时,占用内存31.2MB


测试完我就懵了,因为HttpWebRequest虽然能稳定住内存,
但是使用方法没有HttpClient方便,那么HttpClient到底该如何使用才能稳定住内存呢?
...全文
4652 6 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
易刀锋 2020-02-12
  • 打赏
  • 举报
回复
引用 4 楼 github_36000833 的回复:
HttpClient可以复用,在高吞吐的应用中,也应该复用,否则可能照成Socket资源紧张(Socket关闭后,根据TCP协议的安全指引,要‘冷处理’一会后,才能被重新使用,Windows下可以是2分钟/4分钟)。

HttpClient实践上是线程安全的,微软也建议复用。
[quote=引用 : https://docs.microsoft.com/zh-cn/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client]
HttpClient仅可在应用程序的整个生命周期中实例化一次并重复使用。 以下情况可能会导致SocketException错误:
* 为每个请求创建一个新的HttpClient实例。
* 负载较重的服务器。
为每个请求创建新的HttpClient实例可能会耗尽可用的套接字。

注意上面的中文翻译比较苛刻,英文实际上说HttpClient is intended to be instantiated once...
英文的语气是建议而不是命令。

复用HttpClient可以根据请求baseUrl,根据Host/port等来归类复用,比如学习(甚至直接用)Flurl第三方nuget包。
复用HttpClient也可以在一个客户类下,共用一个(private static)HttpClient。

[/quote]
主要是Cookie问题,每一个类都需要独立的CookieContainer来管理自己的Cookie,但是HttpClientHandler在HttpClient发送过一次请求后就不能修改任何属性了,单例的话没办法做到每个对象独立Cookie管理,我也是比较搞不懂,为什么要设置成发送一次请求后就不允许修改属性
正怒月神 2020-02-12
  • 打赏
  • 举报
回复
HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。 实例化每个请求的 HttpClient 类将耗尽重负载下可用的插槽数。 这将导致 SocketException 错误。 httpClient的帮助类,你直接设置成单例模式就可以了。 他内部方法都是线程安全的,
github_36000833 2020-02-12
  • 打赏
  • 举报
回复
HttpClient可以复用,在高吞吐的应用中,也应该复用,否则可能照成Socket资源紧张(Socket关闭后,根据TCP协议的安全指引,要‘冷处理’一会后,才能被重新使用,Windows下可以是2分钟/4分钟)。 HttpClient实践上是线程安全的,微软也建议复用。
引用 : https://docs.microsoft.com/zh-cn/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client
HttpClient仅可在应用程序的整个生命周期中实例化一次并重复使用。 以下情况可能会导致SocketException错误: * 为每个请求创建一个新的HttpClient实例。 * 负载较重的服务器。 为每个请求创建新的HttpClient实例可能会耗尽可用的套接字。
注意上面的中文翻译比较苛刻,英文实际上说HttpClient is intended to be instantiated once... 英文的语气是建议而不是命令。 复用HttpClient可以根据请求baseUrl,根据Host/port等来归类复用,比如学习(甚至直接用)Flurl第三方nuget包。 复用HttpClient也可以在一个客户类下,共用一个(private static)HttpClient。
  • 打赏
  • 举报
回复
.net 中最近几年加入的异步的 http 请求,不仅仅有“耗内存”问题,而且有逻辑问题,经常造成返回结果张冠李戴的逻辑错误。我们经过了长达一年的测试,在好几个项目上栽过,无法解释是我们的编程问题,只能解释为 .net framework 底层错误。已经经用异步 http 请求。
清晨曦月 2020-02-11
  • 打赏
  • 举报
回复
看看析构函数是不是按照要求进行析构的
清晨曦月 2020-02-11
  • 打赏
  • 举报
回复
用完了释放的不彻底呗。从你的垃圾回收代码里找一找原因。

111,097

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧