C#中的废物 WebRequest

2018-09-10 17:13:29 +08:00
 xiangyuecn

吐槽+骂街贴:吃屎的.net ,一个基本的 http 请求也搞的这么难用,因为一个功能放弃一门(暂且叫语言吧)。

一直用的 WebRequest,直到今天...

1. 完全没法根据需要设定超时

有 Timeout、ReadWriteTimeout ?没用的!看 MSDN 的解释:

https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.timeout(v=vs.110).aspx 域名系统 (DNS) 查询可能需要最多 15 秒钟才能返回或超时。 如果您的请求包含需要解析的主机名,并设置 Timeout 为一个值小于 15 秒,可能需要 15 秒或更长之前 WebException 引发来指示您的请求超时

至少包含一个隐藏的不可控因素,也就是说你没法简单的完全控制请求超时,Timeout 完全是摆设,TMD 的.net 侮辱了 Timeout 这个单词。

ReadWriteTimeout 算是摆设吧,是数据流 read、write 调用超时,意思相近于发送一个直接后等待下一个字节的时间不能超过这个,并非 100%读取完数据和发送完数据的超时控制。然后他默认的 5 分钟完全是废物设定,没错,废物。( java 的 HttpURLConnection 有个设定好像也是如此吧记得)。

没有 ConnectTimeout 选项。没有 ConnectTimeout 选项。没有 ConnectTimeout 选项。(根源所在)

2. 曲线救国 BeginGetRequestStream

使用异步的发送数据流可以变相实现 ConnectTimeout,拿到请求数据流代表已经连上了服务器。

但 GET、HEAD 明确不支持获取 RequestStream。意思就是说 GET 请求你往请求体里面塞东西,.net 就把你写的渣渣代码给干掉,从源头上拒绝你的操作。看到这个逼玩意怎么一个火字了得。

劳资发个请求你还要管我的 method 是什么!

GET 没法拿到请求数据流,GET 没法拿到请求数据流,GET 没法拿到请求数据流。(已经过了底线)

WebRequest 没法实现

比如:严格控制一个 GET 请求耗时在 3 秒以内,超过时间就超时,用来检测 url 是否可以访问。遇到类似 www.google.com 就,,,呵呵了。POST 用 BeginGetRequestStream 没这个问题

要自己实现整体的请求超时功能,他不支持,开个线程也好,开个计时器也好。

GET 不让发请求体,可能很科学,但遇上真不爽

我只是想拿一下发送请求的数据流而已,并非真要往 stream 里面塞数据。

放弃.net

用第三方库解决此类问题?关键是框架给的功能已经能满足 98%的功能了,卡在了这 2%必须实现的功能上而已。用第三方的是不可能的,这辈子都不可能为了这么小小的功能 load 一个 100M 的第三方库的。

so..用 java 吧,或者 php 也行,至少写一个简单的网络请求没这糟心。遇到忍无可忍的地方就换个语言,不是针对.net ,我是说所有的语言!

(文中 C#语言==.net 框架 并非你异议中的 C#语言!=.net 框架,不服去车上拿刀,拿紧)

10963 次点击
所在节点    程序员
84 条回复
xeaglex
2018-09-13 06:42:28 +08:00
@xiangyuecn 不是不能突发操作,是默认预构造的线程较少,当需求不能满足,会有新的线程入池,这个过程慢于普通线程的构造。新入池的线程不会销毁,所以后面就会快了
xeaglex
2018-09-13 06:46:58 +08:00
@xiangyuecn 如果你一定要启动快点,设置下最小线程数就行了
xiangyuecn
2018-09-13 10:01:15 +08:00
@xeaglex 测试了一下调大了最小线程数,新开的线程立马就飚了起来。

刚刚,细细思考一下 ThreadPool 这种延迟开新线程的行为存在的意义,发现一个事实:纯粹是微软某个程序员意淫出来的结果。

[1]

一般常见的池类配置存在两个控制参数:MaxActive 最大活跃数、MaxIdle 最大空闲数;你需要连接立即给你,只要不超过最大连接数,并且池内保留已开启的。

.net ThreadPool:只有 MaxThread 最大活跃数;你需要线程?看你设置了 MinThreads 没有,已开线程数量超过 MinThreads ?不好意思,新血液小水管慢慢流,等着业务超时吧。


[2]

ThreadPool 这个延迟功能初衷本来是好的,可以防止程序因为突然不好的原因( bug 或者其他非预期因素)打开大量线程产生的问题 [非业务需求] 。但如果程序确实需要大量线程来处理,不管你怎么延迟,最终还是会开到满足需要的线程数量 [业务实际需求] ;此时 ThreadPool.SetMinThreads 入场。

但是因为程序功能需要,往往需要调用 ThreadPool.SetMinThreads 调大可快速开启的线程数量,由于 ThreadPool.SetMinThreads 算是一个竞争资源(下面 [3] 解释)。最终的设定会趋向于 ThreadPool.SetMinThreads==ThreadPool.SetMaxThreads,并且趋向于全局只初始化设定一次(如程序启动时)。

最终结果就是 ThreadPool.SetMinThreads 失去存在的意义,ThreadPool 延迟开新线程没有任何价值。


[3]

ThreadPool.SetMinThreads 存在竞争因素,不敢随意调用,最终结果趋向于在程序初始化时设定 ThreadPool.SetMinThreads==ThreadPool.SetMaxThreads。

注意:MinThreads 数量并非会保留的空闲( Idle )线程数量,是要新开线程时如果已开启线程数量没有超过 MinThreads 就会立即开启,否则等着吧。新开的线程超过时间没用会自动关闭,具体保留多少个空闲( Idle )线程,没有设置方法。

默认:SetMinThreads 10
事物 A:事物开始时 SetMinThreads 200 (实际需要 100-300 个线程),事物结束还原成 10
事物 B:事物开始时 SetMinThreads 50 (实际需要 50-80 个线程),事物结束还原成 10

竞争情况:AB 都在运行(无需管谁先开始的),A 需要 30 分钟,B 需要 5 分钟,不管谁先结束,MinThreads 都会变成比另外事物开始时设定值小,都 [有可能] 对另外一个事物产生影响。

解决办法:那就不在事物开始时设定,改成每次从线程池中拿线程时设定 MinThreads,问题解决!

然后演变变成了:

MinThreads=谁大谁说了算;

整个程序 MinThreads≈会最大设定那个值。

整个程序 MinThreads≈MaxThreads。


[结论]

当有相对多点的线程需求时,ThreadPool 这种延迟开新线程的行为没有意义。ThreadPool.SetMinThreads 废物,不如换成 SetMaxIdleThreads。

new Thread(run).Start()没这种问题,但线程启动耗时太长(到无法接受的程度)。ThreadPool.QueueUserWorkItem(run)和 Task.Run(run)共享 ThreadPool。

实现线程池比实现 HttpClient 简单 99 倍,是时候自己实现一个.net 的线程池了(大哭

-----

洋洋洒洒又写作文,测试+码字耗时 1 小时,学习就是这样的 @karllynn #70
xeaglex
2018-09-13 23:06:45 +08:00
@xiangyuecn 额,也许你对线程池上下限有点误解。根据业务拥堵程度动态调整线程池容量不是合适的做法,无上限的资源池+无延迟的入池速度更是危险的东西,试想如果你的业务遭遇 DOS,你这样做是什么后果?设置下限的意义在于节约资源,设置上限的意义在于防止环境崩溃。你应该知道你的服务需要承受多大的压力,在压力到来前尽可能节约系统资源,并在压力到来后减小对外部环境的影响。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/487915

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX