首页 新闻 会员 周边

C#中遇到HttpClient耗时干扰问题

0
悬赏园豆:200 [已解决问题] 解决于 2017-08-25 01:54

最近在使用HttpClient的过程中,发现在定时器中执行HttpClient请求时,会影响到其他工作线程中的HttpClient请求,导致请求耗时延长。

以下是完整测试代码:

 1 using System;
 2 using System.Diagnostics;
 3 using System.Linq;
 4 using System.Net;
 5 using System.Net.Http;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace HttpClientTest
10 {
11     class Program
12     {
13         private const string _url = "http://localhost";
14         private static readonly Timer _timer = new Timer(TimerGet);
15         private static readonly HttpClient _timerClient = new HttpClient(new MyMessageHandler());
16         private static readonly HttpClient _workerClient = new HttpClient(new MyMessageHandler());
17 
18         static void Main(string[] args)
19         {
20             _timer.Change(5000, 5000); // Without this code, it works fine
21 
22             while (true)
23             {
24                 ParallelEnumerable.Range(0, 1000).WithDegreeOfParallelism(16).ForAll(i => WorkerGet());
25                 Console.WriteLine(DateTime.Now.ToString("mmssfff"));
26             }
27         }
28 
29         private static void TimerGet(object state = null)
30         {
31             var sw = Stopwatch.StartNew();
32             var response = _timerClient.GetAsync(_url).Result;
33             sw.Stop();
34 
35             Console.WriteLine("TimerGet:{0}ms", sw.ElapsedMilliseconds);
36         }
37 
38         private static void WorkerGet()
39         {
40             Thread.Sleep(1);
41 
42             var sw = Stopwatch.StartNew();
43             var response = _workerClient.GetAsync(_url).Result;
44             sw.Stop();
45 
46             if (sw.ElapsedMilliseconds > 200)
47             {
48                 Console.WriteLine("WorkerGet:{0}ms", sw.ElapsedMilliseconds);
49             }
50         }
51 
52         public class MyMessageHandler : HttpMessageHandler
53         {
54             protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
55             {
56                 var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("OK") };
57 
58                 var source = new TaskCompletionSource<HttpResponseMessage>();
59                 source.SetResult(response);
60                 return await source.Task;
61             }
62         }
63     }
64 }

运行后需要观察一会,会有概率出现如下情况:

 1 4102120
 2 4102628
 3 4103134
 4 WorkerGet:511ms
 5 WorkerGet:512ms
 6 TimerGet:511ms
 7 WorkerGet:510ms
 8 WorkerGet:510ms
 9 4104159
10 4104670
11 4105192
12 4105596
13 4106045
14 4106549
15 4107054
16 4107578
17 4108081
18 WorkerGet:510ms
19 TimerGet:510ms
20 WorkerGet:510ms
21 WorkerGet:510ms
22 WorkerGet:511ms
23 4109115
24 4109618
25 4110119
26 4110623
27 4111125
28 4111630
29 4112165
30 4112541
31 4112996
32 4113521
33 TimerGet:0ms
34 4114027
35 4114530
36 4115034
37 4115544

WorkerGet和TimerGet互相干扰,导致耗时延长。

如果把 _timer.Change(5000, 5000); 一行注释掉,则无任何问题。

尝试过使用同一个HttpClient实例、把定时器替换成异步线程内循环等,均无效果。

请教如何消除干扰?

----------------------------------------------

貌似和CPU核数有关,如果所开线程数小于CPU核数,就不会出问题。

测试环境:WIN 10,VS2015,.NET 4.5,CPU i5-4200M,内存8G

Herb的主页 Herb | 初学一级 | 园豆:6
提问于:2017-07-15 01:30
< >
分享
最佳答案
0

我这边测试,没有遇到这些问题呢。

using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;

namespace DESTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //TestDesHelper();
            TestTimerHttpClient();
            Console.ReadKey();
        }

        private const string _url = "https://q.cnblogs.com/q/96524/";
        private static readonly Timer _timer = new Timer(TimerGet);
        private static readonly HttpClient _timerClient = new HttpClient();
        private static readonly HttpClient _workerClient = new HttpClient();

        static void TestTimerHttpClient()
        {

            _timer.Change(5000, 5000); // 定时器执行时,有一定概率会导致工作线程HttpClient请求耗时延长

            while (true)
            {
                ParallelEnumerable.Range(0, 10)
                    .WithDegreeOfParallelism(4)
                    .ForAll(i => WorkerGet());
                Console.WriteLine(DateTime.Now.ToString("mm:ss:fff"));
            }

        }
        private static void TimerGet(object state = null)
        {
            var sw = Stopwatch.StartNew();
            var response = _timerClient.GetAsync(_url).Result;
            sw.Stop();

            Console.WriteLine("TimerGet:{0}ms", sw.ElapsedMilliseconds);
        }

        private static void WorkerGet()
        {
            var sw = Stopwatch.StartNew();
            var response = _workerClient.GetAsync(_url).Result;
            sw.Stop();

            if (sw.ElapsedMilliseconds > 200)
            {
                Console.WriteLine("WorkerGet:{0}ms", sw.ElapsedMilliseconds);
            }
        }
    }
}

输出结果:

WorkerGet:741ms
WorkerGet:741ms
WorkerGet:741ms
WorkerGet:741ms
WorkerGet:656ms
WorkerGet:656ms
WorkerGet:656ms
WorkerGet:656ms
WorkerGet:796ms
WorkerGet:796ms
42:58:702
WorkerGet:609ms
WorkerGet:612ms
WorkerGet:612ms
WorkerGet:613ms
WorkerGet:704ms
WorkerGet:708ms
WorkerGet:712ms
WorkerGet:713ms
WorkerGet:652ms
WorkerGet:657ms
43:00:682
WorkerGet:658ms
WorkerGet:660ms
WorkerGet:660ms
WorkerGet:661ms
WorkerGet:664ms
WorkerGet:674ms

符合预期。

测试环境: VS2017 Preview ,.Net Core 2.0

收获园豆:100
幻天芒 | 高人七级 |园豆:37175 | 2017-07-15 09:44

最好是访问本机或局域网内的一个地址,访问外网的话由于网络问题,会对测试结果有影响。

环境1:Win 7,VS2013,.NET 4.5
环境2:Win 10,VS2015,.NET 4.5
我这边两个环境都有问题

Herb | 园豆:6 (初学一级) | 2017-07-15 11:52

我更新了下代码,可以再帮忙看下么

Herb | 园豆:6 (初学一级) | 2017-07-15 22:52

@Herb: 输出结果如下:

WorkerGet:9010ms
WorkerGet:3000ms
58:35:341
TimerGet:1ms
58:36:325
58:37:309
58:38:294
58:39:278
58:40:153
WorkerGet:967ms
WorkerGet:968ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
TimerGet:971ms
WorkerGet:969ms
WorkerGet:970ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:969ms
WorkerGet:968ms
WorkerGet:970ms
58:42:107
58:43:091
58:43:923
58:44:637
TimerGet:0ms
58:45:545
58:46:545
58:47:530
58:48:420
58:49:404
58:50:389
TimerGet:0ms
58:51:373
58:52:358
58:53:342
58:54:326

.Net 4.5,不符合预期

幻天芒 | 园豆:37175 (高人七级) | 2017-07-15 23:59

@幻天芒: 谢谢,估计是和CPU核数有关,不过你这咋这么慢。。。

Herb | 园豆:6 (初学一级) | 2017-07-16 00:02

@Herb: 这个结果不太对,经过多次测试,后面的情况下已经符合预期了。

幻天芒 | 园豆:37175 (高人七级) | 2017-07-16 00:43
WorkerGet:2ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
417   45:53:129
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:1ms
WorkerGet:0ms
WorkerGet:0ms
418   45:53:160
WorkerGet:0ms
WorkerGet:1ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
WorkerGet:0ms
幻天芒 | 园豆:37175 (高人七级) | 2017-07-16 00:46

@Herb: 

static void Main(string[] args)
        {
            // 第一个参数代表多少毫秒后开始执行
            // 第二个参数代表,每次执行间隔多少毫秒
            _timer.Change(0, 5000); // Without this code, it works fine
            var n = 0;
            while (n < 2)
            {
                n++;
                var b = n;
                ParallelEnumerable.Range(0, 8) // 定义多少个并行任务
                    .WithDegreeOfParallelism(8) // 同时执行的最大并行任务数
                    .ForAll(i => WorkerGet());
                // 等上面的任务执行完成后,会执行这句输出代码(执行完成,是指WorkerGet都已经执行完毕)
                Console.WriteLine(b + "   " + DateTime.Now.ToString("mm:ss:fff"));
            }
            Console.ReadKey();
        }

        private static void TimerGet(object state = null)
        {
            var sw = Stopwatch.StartNew();
            var response = _timerClient.GetAsync(_url).Result;
            // 在输出get和TimeGet之间,有可能会插入ok的输出,这个看运气。
            Console.WriteLine("get");
            sw.Stop();
            Console.WriteLine("TimerGet:{0}ms --- {1}", sw.ElapsedMilliseconds, DateTime.Now.ToString("mm:ss:fff"));
        }

        private static void WorkerGet()
        {
            Thread.Sleep(1);

            var sw = Stopwatch.StartNew();
            var response = _workerClient.GetAsync(_url).Result;
            sw.Stop();
            Console.WriteLine("ok");
        }

我加了些注释,已经我改变后的代码,你可以再仔细测试下。

幻天芒 | 园豆:37175 (高人七级) | 2017-07-16 00:55

@幻天芒: 谢谢,依然有问题,中间等了4秒钟

get
TimerGet:70ms --- 59:43:813
ok
ok
ok
ok
ok
ok
ok
ok
1   59:47:811
ok
ok
ok
ok
ok
ok
ok
ok
2   59:47:815
Herb | 园豆:6 (初学一级) | 2017-07-16 01:03

我想要的结果是,每次调用Get都应该保持在200ms以下,所以我打印以下代码来判断是否出问题。

if (sw.ElapsedMilliseconds > 200)
{
     Console.WriteLine("WorkerGet:{0}ms", sw.ElapsedMilliseconds);
}
Herb | 园豆:6 (初学一级) | 2017-07-16 01:10

@Herb: 感觉同样的代码,测试结果很不稳定。

幻天芒 | 园豆:37175 (高人七级) | 2017-07-16 11:37
其他回答(7)
0

没看到没有干扰的数据,无可置评。

收获园豆:20
爱编程的大叔 | 园豆:30839 (高人七级) | 2017-07-15 09:33
0

符合预期。

测试环境: VS2017 Preview ,.Net Core 2.0

收获园豆:20
fcyh | 园豆:568 (小虾三级) | 2017-07-15 11:40
0

filder看看网络请求耗时情况先

收获园豆:20
czd890 | 园豆:14412 (专家六级) | 2017-07-15 22:29

我把访问网络去掉了,一样会互相干扰

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-16 00:03
0

问题解决了,同步一下结果。

            // 方法一:有问题
            //new Timer(TimerGet, null, 5000, 5000);

            // 方法二:有问题
            //Task.Run(() => TimerTask());

            // 方法三:正常
            new Thread(TimerTask).Start();

把Timer替换成直接开线程,就可以正常运行了。

ThreadPool.QueueUserWorkItem方法和Timer类总是会将工作项放到全局队列中。
由于多个工作者线程可能同时从全局队列中拿走工作项,所以所有工作者线程都竞争一个线程同步锁,从而可能对程序性能造成影响。

参考:http://www.cnblogs.com/x-xk/archive/2012/12/13/2814369.html

Herb | 园豆:6 (初学一级) | 2017-07-16 02:38

还没有完全解决,如果线程开多了,超过CPU核心数,还是会有问题。

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 12:40

是线程池的问题,线程池线程每秒只能创建2个,没有足够的线程又创建不过来导致耗时。

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-08-25 01:57
0

ServicePointManager.DefaultConnectionLimit = Int32.MaxValue;

我记得WebClient限制好像挺多的,自己封装一下HttpWebRequest吧

收获园豆:40
tor2 | 园豆:244 (菜鸟二级) | 2017-07-17 14:14

设置后无效果。
尝试过HttpClient、RestSharp、WebClient、HttpWebRequest,只有HttpClient最快。

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 14:27

@Herb: HttpClient底层是HttpWebRequest

我记得Httpclinet相同host只支持两个线程。

你设置一下DefaultConnectionLimit 然后用HttpWebRequest测试一下

支持(0) 反对(0) tor2 | 园豆:244 (菜鸟二级) | 2017-07-17 19:51

@tor2: 试过了,DefaultConnectionLimit 对HttpClient无效。
对HttpWebRequest确实有效,但是HttpWebRequest有另外的问题,在本机速度快,放到服务器上速度就变得很慢,RestSharp和WebClient也是一样的现象。

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 19:54

@Herb: “在本机速度快,放到服务器上速度就变得很慢” 这个准确的说第一次请求很慢吧?

设置一下request.proxy=null;解决

支持(0) 反对(0) tor2 | 园豆:244 (菜鸟二级) | 2017-07-17 19:55

@tor2: 不是第一次慢,是一直慢,在本机几毫秒的请求,放到服务器上就变成200多毫秒了。
request.proxy=null我等会试试

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 19:57

@Herb: 我一直用的HttpWebRequest很快,但进程同时发起1000个请求没问题

支持(0) 反对(0) tor2 | 园豆:244 (菜鸟二级) | 2017-07-17 19:59

@tor2: 按你说的加了 request.Proxy = null;

这是我本机的结果:

HttpClient耗时:466毫秒
HttpClient耗时:3毫秒
HttpClient耗时:3毫秒
HttpClient耗时:5毫秒
HttpClient耗时:3毫秒
HttpClient耗时:3毫秒
HttpClient耗时:4毫秒
HttpClient耗时:3毫秒
HttpClient耗时:3毫秒
HttpClient耗时:3毫秒
----------------------------------------
RestSharp耗时:33毫秒
RestSharp耗时:3毫秒
RestSharp耗时:4毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
RestSharp耗时:3毫秒
----------------------------------------
HttpWebRequest耗时:9毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:4毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
HttpWebRequest耗时:3毫秒
----------------------------------------
WebClient耗时:3毫秒
WebClient耗时:2毫秒
WebClient耗时:3毫秒
WebClient耗时:3毫秒
WebClient耗时:2毫秒
WebClient耗时:4毫秒
WebClient耗时:2毫秒
WebClient耗时:2毫秒
WebClient耗时:2毫秒
WebClient耗时:2毫秒

这是服务器上的结果:

HttpClient耗时:68毫秒
HttpClient耗时:4毫秒
HttpClient耗时:2毫秒
HttpClient耗时:4毫秒
HttpClient耗时:4毫秒
HttpClient耗时:4毫秒
HttpClient耗时:4毫秒
HttpClient耗时:4毫秒
HttpClient耗时:3毫秒
HttpClient耗时:3毫秒
----------------------------------------
RestSharp耗时:249毫秒
RestSharp耗时:210毫秒
RestSharp耗时:218毫秒
RestSharp耗时:211毫秒
RestSharp耗时:210毫秒
RestSharp耗时:216毫秒
RestSharp耗时:203毫秒
RestSharp耗时:218毫秒
RestSharp耗时:201毫秒
RestSharp耗时:219毫秒
----------------------------------------
HttpWebRequest耗时:226毫秒
HttpWebRequest耗时:207毫秒
HttpWebRequest耗时:219毫秒
HttpWebRequest耗时:216毫秒
HttpWebRequest耗时:203毫秒
HttpWebRequest耗时:201毫秒
HttpWebRequest耗时:219毫秒
HttpWebRequest耗时:216毫秒
HttpWebRequest耗时:202毫秒
HttpWebRequest耗时:203毫秒
----------------------------------------
WebClient耗时:218毫秒
WebClient耗时:199毫秒
WebClient耗时:212毫秒
WebClient耗时:209毫秒
WebClient耗时:201毫秒
WebClient耗时:220毫秒
WebClient耗时:216毫秒
WebClient耗时:202毫秒
WebClient耗时:201毫秒
WebClient耗时:202毫秒
支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 20:10

@Herb: 看一下HttpWebRequest是不是HTTP协议是不是1.1

支持(0) 反对(0) tor2 | 园豆:244 (菜鸟二级) | 2017-07-17 20:11

@tor2: 是HTTP1.1

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 20:28

@tor2: 我把本机访问的域名改成IP之后,和服务器一样也变成200ms了。[笑哭]

支持(0) 反对(0) Herb | 园豆:6 (初学一级) | 2017-07-17 20:32
0

这里可能有你想要的答案  https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/   

10小虎 | 园豆:202 (菜鸟二级) | 2017-11-16 19:52
0

只要用task或者threaddpool发起的多线程,内部用httpclient转同步,都要卡死一会,查了一天,最终new thread解决,这个页面打开过好几次,最后一次注意到了开线程的方式。

Coderrrrrr | 园豆:212 (菜鸟二级) | 2019-11-08 13:26
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册