吐槽+骂街贴:吃屎的.net ,一个基本的 http 请求也搞的这么难用,因为一个功能放弃一门(暂且叫语言吧)。
有 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 选项。(根源所在)
使用异步的发送数据流可以变相实现 ConnectTimeout,拿到请求数据流代表已经连上了服务器。
但 GET、HEAD 明确不支持获取 RequestStream。意思就是说 GET 请求你往请求体里面塞东西,.net 就把你写的渣渣代码给干掉,从源头上拒绝你的操作。看到这个逼玩意怎么一个火字了得。
劳资发个请求你还要管我的 method 是什么!
GET 没法拿到请求数据流,GET 没法拿到请求数据流,GET 没法拿到请求数据流。(已经过了底线)
比如:严格控制一个 GET 请求耗时在 3 秒以内,超过时间就超时,用来检测 url 是否可以访问。遇到类似 www.google.com 就,,,呵呵了。POST 用 BeginGetRequestStream 没这个问题
要自己实现整体的请求超时功能,他不支持,开个线程也好,开个计时器也好。
我只是想拿一下发送请求的数据流而已,并非真要往 stream 里面塞数据。
用第三方库解决此类问题?关键是框架给的功能已经能满足 98%的功能了,卡在了这 2%必须实现的功能上而已。用第三方的是不可能的,这辈子都不可能为了这么小小的功能 load 一个 100M 的第三方库的。
so..用 java 吧,或者 php 也行,至少写一个简单的网络请求没这糟心。遇到忍无可忍的地方就换个语言,不是针对.net ,我是说所有的语言!
(文中 C#语言==.net 框架 并非你异议中的 C#语言!=.net 框架,不服去车上拿刀,拿紧)
1
ruatyy 2018-09-10 17:17:49 +08:00 1
兄弟早点转行吧,.net 不行了,微软块倒闭了。
对了,世界上最好的语言是 PHP,滑稽.jpg |
2
cjw1115 2018-09-10 17:23:29 +08:00 7
WebRequst 是什么??难道不是用 HttpClient 吗?
|
4
xiangyuecn OP |
5
ZhLTE 2018-09-10 17:30:25 +08:00
php 是世界上最好的语言! 什么语言 get 可以发数据流啊
|
6
fgodt 2018-09-10 17:33:48 +08:00
阻碍一个人的永远不是语言
|
7
xiangyuecn OP @ZhLTE 比如 get 一下 www.dropbox.com ,能达到预期( 2 秒超时就是 2 秒超时)的语言就是 ok,.net 就是渣渣
|
8
catcn 2018-09-10 17:35:55 +08:00
var a = document.getElementsByName('description')[0].content;
console.info(a.substring(27, 29)); |
9
yulitian888 2018-09-10 17:36:10 +08:00 5
WebClient、HttpClient、HttpWebRequest 的区别不打算了解一下?
|
10
ZhLTE 2018-09-10 17:37:19 +08:00
你是想要一个 无论数据是否传输 connect 时间 2s 就算超时的方法吗
|
11
xiangyuecn OP @catcn 刀呢
|
12
enenaaa 2018-09-10 17:38:09 +08:00
能写这么长的文来喷也是真爱
|
13
xiangyuecn OP @yulitian888 字面意思 HttpClient extend WebClient,WebClient using HttpWebRequest,仅字面意思
@ZhLTE 差不多这意思,从请求创建开始,不管是哪里卡了,只要到时间还么有最终结果就是超时 |
14
xupefei 2018-09-10 17:47:32 +08:00 via Android
把这个连接扔到 Task 里也不行?
|
15
daigouspy 2018-09-10 17:49:56 +08:00 via Android 5
@xiangyuecn 包在一个线程里面,时间到就 abort,绝对好使。你的需求是应用层面了,不应该要求类库自带,类库只是提供基本需求,你的需求需要自己包装。
|
16
ZhLTE 2018-09-10 17:54:59 +08:00 1
可以试试 task 的 CancellationTokenSource
|
17
luozic 2018-09-10 17:55:26 +08:00 2
丰富了 block 名单
|
18
crab 2018-09-10 17:56:46 +08:00
winhttp
|
19
xiangyuecn OP |
20
zkd8907 2018-09-10 17:58:03 +08:00
blockList.push(this);
|
21
cdwyd 2018-09-10 17:58:52 +08:00 via Android 2
c#真是垃圾语言,竟然不能自动赚钱
|
22
xiangyuecn OP |
23
AngelCriss 2018-09-10 18:02:28 +08:00 via Android
自己造个轮子不就好了
|
24
xupefei 2018-09-10 18:03:04 +08:00 via Android 1
@xiangyuecn 注意你的语气。
Task 是在线程池上的,和 Thread 不一样。Thread 是最重的,但是要说花掉 50ms 我觉得还是不可能的。 |
25
fwee 2018-09-10 18:04:36 +08:00
LZ 得换个 GET 能发请求体的语言
|
26
xiangyuecn OP |
27
qdwang 2018-09-10 18:15:07 +08:00 1
.net 库在易用性上确实做的不好,我觉得可能是因为发展的时候,没有得到很多类似楼主这样的反馈的缘故。
|
28
xiangyuecn OP @fwee 研究 get request body 的时候发现的一篇文章: https://blog.csdn.net/q_an1314/article/details/51298073,我不是要用 GET 来发请求体,而是遇上了需要拿到请求 Stream 才能处理 ConnectTimeout,但 GET 不让拿 RequestStream,导致 GET 部分无解,真遇上了“ GET 不让发请求体”,有点为难
|
29
oyjc 2018-09-10 18:35:30 +08:00
开个 socket 自己连接不行么 --!
|
30
verrickt 2018-09-10 19:05:54 +08:00 1
@xiangyuecn
>完全没法根据需要设定超时 查了下,HttpClient 的 timeout 也包含了[DNS 查询]( https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=netframework-4.7.2#System_Net_Http_HttpClient_Timeout) 不过 LZ 可以试试这样 ``` csharp var client = new HttpClient(); var cts = new CancallationTokenSource(); var task = client.GetAsync("http://example.com",cts.Token); cts.CancelAfter(TimeSpan.FromSeconds(2)); try { var response = await task; response.EnsureSuccess(); } catch(TaskCancelldException) { // time out } ``` 跟 LZ 一起吐槽,HTTPClient 完全无法得知上传进度。 神奇的是,官方不推荐的的`System.Web.WebClient`和`Windows.Web.Http`是可以得到上传进度的,亲儿子`System.Web.Http`却不行 workaround 有 - 继承`StreamContent `用`SerializeToStreamAsync`去做手脚。 - 写个 Wrapper 流。 两者都非常丑,并且都只能得到流被序列化的进度,无法得到网络传输的进度。 Bonus [论打开自动更新的重要性]( https://verrickt.github.io/2018/05/21/hell-mode-debugging/) |
31
ShareDuck 2018-09-10 19:08:41 +08:00 via Android
先收藏这个主题,回去试试。
|
32
xiangyuecn OP |
33
hellohello123 2018-09-10 20:13:06 +08:00
歪个楼,这个 C# 这门语言有什么关系,明明是类库实现的问题,最多吐槽 .NET
|
34
liuguang 2018-09-10 21:35:24 +08:00
哈哈,自己学的不深,不会用也不要把黑锅往语言上扔啊。
https://docs.microsoft.com/zh-cn/dotnet/api/system.net.webrequest.timeout?view=netframework-4.7.2#System_Net_WebRequest_Timeout timeout 可以设置的。。。 |
35
liuguang 2018-09-10 21:37:07 +08:00
下载的话,想要进度条,用 WebClient 啊,异步下载,有各种事件
|
36
verrickt 2018-09-10 22:08:13 +08:00 1
@xiangyuecn 另一个思路,把 WebRequest 的 BeginXXX/EndXXX 转换成 Task,然后用 CancellationToken
相关链接 https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory-1.fromasync?view=netframework-4.7.2 https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types https://blog.stephencleary.com/2012/07/async-interop-with-iasyncresult.html |
37
lolizeppelin 2018-09-10 22:10:46 +08:00 via Android
get 发流是可以的 用 post 然后覆盖请求方法
标准的 http 服务都会认的 |
38
loqixh 2018-09-10 22:22:49 +08:00
get 什么时候不可以发请求体? 用 SendAsync 就可以了
|
39
xiangyuecn OP @liuguang public abstract class WebRequest
abstract 需要查实现 public class HttpWebRequest : System.Net.WebRequest 然后会发现帖子正文中的引用内容 |
40
xiangyuecn OP @verrickt 嗯,学习了。IAsyncResult 估计是历史包袱。
|
41
forerunner 2018-09-10 23:08:51 +08:00
然后再次确认一下,你用的是 .net framework 还是 .net core ?版本是什么?不知道我为何这么问的话,出门左转 Java 组欢迎你……
|
42
xiangyuecn OP |
43
nannanziyu 2018-09-11 00:23:46 +08:00 5
@xiangyuecn
用 HttpClient 的话,一行解决 await new HttpClient().GetAsync("http://www.baidu.com",new CancellationTokenSource(2000).Token); 要用 WebRequest 的话,也是一行解决 var task = await Task.WhenAny(WebRequest.CreateHttp("http://www.baidu.com").GetResponseAsync(), Task.Delay(2000)); // then if (task is Task<WebResponse>) { var r = await (task as Task<WebResponse>); } else { // timeout } 楼主有吐槽的时间,不如多学学基础知识…… |
44
longaiwp 2018-09-11 00:36:35 +08:00
快转行吧,.NET 不行了,微软要倒闭了。你去学 PHP 吧,世界上最好的语言
|
45
nannanziyu 2018-09-11 00:36:58 +08:00
另外
“ get 里发 body ” “拿到请求 Stream 才能处理 ConnectTimeout ” “ task 下来可能额外需要等待 0-50ms ” 我想调侃两句都无从下嘴,这对 http 和 thread 一点概念都没有。。。 |
46
20150517 2018-09-11 00:43:26 +08:00 via Android
|
47
xiaojunjor 2018-09-11 08:47:09 +08:00
看了这个贴到让我发现站里的.net 程序员真不少
|
48
keymao 2018-09-11 08:58:38 +08:00 1
先说下结论,.Net 的 WebRequest... 说实在的 之前做过贴吧的一些辅助工具的开发,后来全是用 HttpClient 自己封装工具,不要指望微软给你做好了。 微软就是这样的风格,习惯了就好了。 能用,够用,谈不上好用,自己封装就完了。也别怪微软了,毕竟 wp 这种本来是战略高度的玩意儿都完全玩崩了。
|
49
nannanziyu 2018-09-11 09:20:58 +08:00
|
50
guolaopi 2018-09-11 09:35:17 +08:00
@nannanziyu 别说了。。我要转 java。。。(滑稽)
|
51
passerbytiny 2018-09-11 09:35:24 +08:00 3
“严格控制一个 GET 请求耗时在 3 秒以内,超过时间就超时,用来检测 url 是否可以访问”
对于这种不符合惯例的专用需求,任何语言,包括它们的扩展底层的第三方类库,都不会实现。楼主至少需要先学习下 HTTP 协议的连接过程,深入一点的话还要学习 TCP 协议和 OSI 模型。 你第一段转摘的 MSDN 的文档,已经涉及了 HTTP 协议的一部分: 域名系统 (DNS) 查询可能需要最多 15 秒钟才能返回或超时。ReadWriteTimeout 的超时也是完全没问题的,传递大量数据(比如下载文件),你觉得以下这两种情况哪种会提前检测到超时:预估 60 分钟下载完,60 分钟还没下载完,算超时; 5 分钟没有读写数据,算超时。 楼主以为 Java 能实现,我想你只是看了 javadoc,而没有测试。测试完估计你又要骂 Java 废物了。 通常,服务器需要高并发所以必须能精确控制超时,客户端无需高并发所以不需要精确控制超时。楼主现在需要客户端精确控制超时,非惯例需求,就要自己实现。请在开骂前先想想自己是多数还是少数,别一不小心就当了代表。 |
52
xuanbg 2018-09-11 09:44:40 +08:00
哎。。。我给 HTTPWebRequest 封装了两层,第一层是通用封装,只是抽象了调用方法,简化了参数。第二层是我项目的专用封装,进一步简化了参数。楼主你想要方便、好用还通用,建议你也做两层封装。
|
53
weizhen199 2018-09-11 10:06:32 +08:00
HttpClient 啦,那个早不用了
|
54
dhssingle 2018-09-11 10:07:09 +08:00
坐等楼主来喷 Java 和 PHP
|
55
arsom 2018-09-11 10:09:08 +08:00 via iPhone
别人喷个 net,非要扯 php。我觉得有些 v 站的人好自卑哈哈
|
56
wizardforcel 2018-09-11 10:13:19 +08:00
根据我多年开发辅助的经验,我的实际测试结果是那两个 Timeout 有用。不信你访问一个比较慢的站点,明显能感觉到设了会立即报错。
|
57
CRVV 2018-09-11 10:34:31 +08:00
@passerbytiny
curl https://icanhazip.com --max-time 0.2 -v Python requests.get('https://icanhazip.com', timeout=0.2) 这俩都可以设不同的时间不同的域名,看到不同的错 别随便用“任何语言”这么强硬的词语 |
58
xiangyuecn OP @nannanziyu 嗯嗯,还在学,新收到 Task.WhenAny、Task.Delay 新知识。
查询了一下 Task.Delay,https://blog.csdn.net/wushang923/article/details/41015063 本质上是开了一个计时器。 试了一下 WebRequest 的 GetRequestStreamAsync、GetResponseAsync,昨天测试没发现这两个方法,和 BeginGetRequestStream 和 BeginGetResponse 一样是异步方法,功能差不多,前面的两个简洁多了。 现在已经换掉了昨天写的 BeginGetRequestStream、BeginGetResponse,直接用的 BeginGetRequestStream().Wait(timeout),这部分代码少了一半。 隐约发现 HttpWebRequest 好像已经过时了。。 ----------- #45 这几点如果抛开上下文来看,确实很滑稽,我现在看一遍我自己也发笑。 -- ConnectTimeout 是 WebRequest 没有提供的,看文档这个应该是和 GetRequestStream 超时差不多,所以 ConnectTimeout≈Timeout(GetRequestStream 之前设置值),但 Timeout 有点小缺陷( dns 查询部分)。 所以就有了“拿到请求 Stream 才能处理 ConnectTimeout ” -- GetRequestStream 方法 GET、HEAD method 是禁止调用的,就有了“ get 里发 body ”,并非真要往 stream 里面塞数据。 -- 一个 task 对象背后的具体实现由于不明确,认定有 0-50ms 延时不为过,参考 Task.Delay,测试过程中发会多出 30ms 左右。 done. ------------------ 其实写这个帖子根源还是我想要一个 ConnectTimeout 设定,因为请求一个 url,如果连接不上服务器应该立即返回错误,而 ConnectTimeout 应该远远小于 GetResponse 的超时。比如:ConnectTimeout=2 秒能满足 99.9%的需求,但 GetResponseTimeout 就需要根据不同业务来定,如访问 V2EX,会 GetResponseTimeout=10 秒,虽然很多情况下 1 秒就能拿到响应,但很多时候 V2 会卡很久才会开始返回响应头(经常刷 V2 就是这个感觉)。 应用场景:用户提交任意 URL,尽快速度测试 URL 是否有效,URL 中 50%可能是被墙地址,比如 google.com ,先决条件:需要让 V2EX 等可能会响应慢的地址尽可能通过测试。 简单有效的解决方案就是使用 ConnectTimeout=2 秒,如果连接不上服务器 2 秒就超时了,不管是被墙还是服务器宕机。GetResponseTimeout=10 秒,让响应慢的地址尽可能通过测试。 回到.net ,POST 可以用 GetRequestStream 来支持 ConnectTimeout。GET 目前学习发现暂时无解,只能等到 GetResponseTimeout 超时。 又洋洋洒洒的写了一篇作文,耗时半小时,划水,划水 ---- @passerbytiny 好吧,发现帖子正文和上面楼层自己回复的和想要的功能有点出入,这个里在详细重新描述了一下。缺乏一个功能肯定会带来某些功能的丧失,我这个请求功能是从 java 里写的一个类中移植过来的,java 里面 HttpURLConnection 可控制的参数多些,有 setConnectTimeout。 |
59
passerbytiny 2018-09-11 11:00:08 +08:00
@CRVV
centos 7 执行结果 # curl https://icanhazip.com --max-time 0.2 -v curl: option --max-time: expected a proper numerical parameter curl: try 'curl --help' or 'curl --manual' for more information Python 的,请上实际测试结果,你怎么就能确定 timeout 指的是命令超时时间,而不是 GET 请求的超时时间。 |
60
keymao 2018-09-11 11:05:58 +08:00
|
61
LokiSharp 2018-09-11 11:12:00 +08:00
C# 挺好,但是 .net 是我见过最垃圾的环境
|
62
xiangyuecn OP @wizardforcel 98%情况下是有用的,我需要那 2%,嘿嘿
|
63
stnaw 2018-09-11 11:26:52 +08:00
C#,你已经是一门成熟的语言了。
该学会自己码代码了 |
64
passerbytiny 2018-09-11 11:27:47 +08:00
@CRVV
curl 我测试了,自己看结果吧。复现很简单,把 DNS 服务器改成无效服务器,然后执行 curl 命令。 2018 年 09 月 11 日 星期二 11:23:18 CST [root@lxl:~]# ping www.sogou.com ping: www.sogou.com: 未知的名称或服务 [root@lxl:~]# date 2018 年 09 月 11 日 星期二 11:23:41 CST [root@lxl:~]# date 2018 年 09 月 11 日 星期二 11:24:06 CST [root@lxl:~]# curl --connect-timeout 1 http://www.sogou.com curl: (28) Resolving timed out after 1510 milliseconds [root@lxl:~]# date 2018 年 09 月 11 日 星期二 11:24:29 CST [root@lxl:~]# date 2018 年 09 月 11 日 星期二 11:25:07 CST [root@lxl:~]# curl --max-time 1 http://www.sogou.com curl: (28) Resolving timed out after 1510 milliseconds [root@lxl:~]# date 2018 年 09 月 11 日 星期二 11:25:30 CST |
65
CRVV 2018-09-11 11:36:58 +08:00
@passerbytiny
man curl -m, --max-time <time> Maximum time in seconds that you allow the whole operation to take. This is useful for preventing your batch jobs from hanging for hours due to slow networks or links going down. Since 7.32.0, this option accepts decimal val- ues, but the actual timeout will decrease in accuracy as the specified timeout increases in decimal precision. 请用一个稍微新一点的 curl 看了一下 requests 的文档,这个确实不是整个操作的超时 |
66
hibobby 2018-09-11 11:42:16 +08:00
这喷的毫无道理了吧,你感觉.net 提供的类库不好用自己拿 C#实现一个啊。
http 协议的底层也是 tcp/IP 自己拿 socket 实现一个 http 协议的 client 不就行了 来喷干啥 |
67
xiangyuecn OP #66 还有 #29 我站着不动,你们去拿刀。
要用 socket 实现 httpclient,不否认可以实现,里面的工作量有多大,写出来比框架自带的是好还是坏,我就不知道了。老是诱导我们这群小白去磨一些很基础但很重要,深入发现里面异常庞大的东西。 |
68
xiangyuecn OP |
69
CRVV 2018-09-11 12:07:41 +08:00
@passerbytiny
这个我也可以复现,看起来像一个 curl 的 bug,他的网站还专门写了 “ When the set time has elapsed, curl will exit no matter what is going on at that moment ” V2EX 说“请不要在每一个回复中都包括外链,这看起来像是在 spamming ”,所以没有加上链接 可以给你另找一个 ``` package main import ( "context" "log" "net/http" "time" ) func main() { req, err := http.NewRequest("GET", "url", nil) if err != nil { log.Fatal(err) } ctx, _ := context.WithTimeout(context.Background(), time.Second) req = req.WithContext(ctx) _, err = http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } } ``` 把 DNS 设成不能用的服务器之后,这段 Go 的输出是 $ time ./timeout 2018/09/11 03:56:53 Get url: context deadline exceeded ./timeout 0.00s user 0.02s system 2% cpu 1.020 total |
70
karllynn 2018-09-11 14:16:26 +08:00
有发帖的时间都解决问题了。。
|
71
rwecho 2018-09-11 16:57:00 +08:00
Task 是 C#灵魂了, 代码比 Begin 更易懂
|
72
mritd 2018-09-11 19:26:50 +08:00 via iPhone
哈哈,有点意思,没用过 C#,不过如果真如楼主所说这个 API 倒是真的反人类,写了好几年 java,最近在写 go,楼主可以看看 go 的标准库 http 请求发送,保准你开森
|
73
mmdsun 2018-09-11 19:37:04 +08:00 via Android
检测 url 可否访问方式很多。。。你开个定时器就好 2 秒没反应直接跳出。或者用 ping 命令。java 一般用第三方的库
|
74
snw 2018-09-11 20:42:03 +08:00 via Android
被 Windows (尤其 Win10 )花式卡住甚至挂死的那么多年了,就知道不该对微软家产品的这方面有任何期待(滑稽)
WebClient 确实最简单易用,其次是 HttpClient,这两个完不成的话那就只能用 HttpWebRequest 写冗长又丑陋的代码了,怨念是必然的。 |
75
reus 2018-09-11 21:38:00 +08:00
@passerbytiny “任何语言”?呵呵。看看 go 的 https://godoc.org/net/http#Transport,里面的 ResponseHeaderTimeout 字段就是控制你写完请求之后,到收到第一个响应头的超时时间,完全切合需求。为什么 go 可以实现?难道 go 实现的 HTTP 协议有什么不同?
|
76
reus 2018-09-11 21:41:44 +08:00
@passerbytiny 更简单的,http.Client 本身就有一个 Timeout 字段: https://godoc.org/net/http#Client ,而且 GET 请求也能带 body。
|
77
paranoiagu 2018-09-11 23:15:48 +08:00 via Android
.net 的网络请求容易卡死。
|
78
wizardforcel 2018-09-12 00:08:33 +08:00 via Android
@xiangyuecn 如果这 2%不是随机出现的,那么就可以自己定制一套东西。比如拿 Socket 或者 TCPClient 封装出来。
|
79
xeaglex 2018-09-12 11:15:09 +08:00
我就想问一个问题,httpClient 多线程下内存泄漏的毛病什么时候修好?
|
80
xiangyuecn OP @rwecho @paranoiagu @xeaglex 从发起到完成一个请求,.net 背后套入太多。
顺带发现一个好玩的: 昨天开 50 个线程(Task 实现的)去请求数据,请求数据中用到了 GetResponseAsync,会发现开始时速度极慢,而且会产生超时请求,2 分钟后请求速度达到最高速率并且稳定,最后发现是线程池的锅。 .net 4.5 中线程池中最小线程数饱和后,会新开新线程。但不是需要多少立即开多少,基本上 1 秒开一个新线程,启动异常缓慢。导致突然增加的 50 个线程只有少量在工作,其他排队。进而导致 GetResponseAsync 没有线程来处理,导致超时。 看 https://bbs.csdn.net/topics/390454205?page=1。 感觉用.net 来做定时采集任务是要废了,请求不好用,线程池还不能进行突发操作,略略略。。。 |
81
xeaglex 2018-09-13 06:42:28 +08:00 via Android
@xiangyuecn 不是不能突发操作,是默认预构造的线程较少,当需求不能满足,会有新的线程入池,这个过程慢于普通线程的构造。新入池的线程不会销毁,所以后面就会快了
|
82
xeaglex 2018-09-13 06:46:58 +08:00 via Android
@xiangyuecn 如果你一定要启动快点,设置下最小线程数就行了
|
83
xiangyuecn OP @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 |
84
xeaglex 2018-09-13 23:06:45 +08:00
@xiangyuecn 额,也许你对线程池上下限有点误解。根据业务拥堵程度动态调整线程池容量不是合适的做法,无上限的资源池+无延迟的入池速度更是危险的东西,试想如果你的业务遭遇 DOS,你这样做是什么后果?设置下限的意义在于节约资源,设置上限的意义在于防止环境崩溃。你应该知道你的服务需要承受多大的压力,在压力到来前尽可能节约系统资源,并在压力到来后减小对外部环境的影响。
|