最近在使用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
我这边测试,没有遇到这些问题呢。
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
最好是访问本机或局域网内的一个地址,访问外网的话由于网络问题,会对测试结果有影响。
环境1:Win 7,VS2013,.NET 4.5
环境2:Win 10,VS2015,.NET 4.5
我这边两个环境都有问题
我更新了下代码,可以再帮忙看下么
@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,不符合预期
@幻天芒: 谢谢,估计是和CPU核数有关,不过你这咋这么慢。。。
@Herb: 这个结果不太对,经过多次测试,后面的情况下已经符合预期了。
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
@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"); }
我加了些注释,已经我改变后的代码,你可以再仔细测试下。
@幻天芒: 谢谢,依然有问题,中间等了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
我想要的结果是,每次调用Get都应该保持在200ms以下,所以我打印以下代码来判断是否出问题。
if (sw.ElapsedMilliseconds > 200)
{
Console.WriteLine("WorkerGet:{0}ms", sw.ElapsedMilliseconds);
}
@Herb: 感觉同样的代码,测试结果很不稳定。
没看到没有干扰的数据,无可置评。
符合预期。
测试环境: VS2017 Preview ,.Net Core 2.0
filder看看网络请求耗时情况先
我把访问网络去掉了,一样会互相干扰
问题解决了,同步一下结果。
// 方法一:有问题
//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
还没有完全解决,如果线程开多了,超过CPU核心数,还是会有问题。
是线程池的问题,线程池线程每秒只能创建2个,没有足够的线程又创建不过来导致耗时。
ServicePointManager.DefaultConnectionLimit = Int32.MaxValue;
我记得WebClient限制好像挺多的,自己封装一下HttpWebRequest吧
设置后无效果。
尝试过HttpClient、RestSharp、WebClient、HttpWebRequest,只有HttpClient最快。
@Herb: HttpClient底层是HttpWebRequest
我记得Httpclinet相同host只支持两个线程。
你设置一下DefaultConnectionLimit 然后用HttpWebRequest测试一下
@tor2: 试过了,DefaultConnectionLimit 对HttpClient无效。
对HttpWebRequest确实有效,但是HttpWebRequest有另外的问题,在本机速度快,放到服务器上速度就变得很慢,RestSharp和WebClient也是一样的现象。
@Herb: “在本机速度快,放到服务器上速度就变得很慢” 这个准确的说第一次请求很慢吧?
设置一下request.proxy=null;解决
@tor2: 不是第一次慢,是一直慢,在本机几毫秒的请求,放到服务器上就变成200多毫秒了。
request.proxy=null我等会试试
@Herb: 我一直用的HttpWebRequest很快,但进程同时发起1000个请求没问题
@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毫秒
@Herb: 看一下HttpWebRequest是不是HTTP协议是不是1.1
@tor2: 是HTTP1.1
@tor2: 我把本机访问的域名改成IP之后,和服务器一样也变成200ms了。[笑哭]
这里可能有你想要的答案 https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
只要用task或者threaddpool发起的多线程,内部用httpclient转同步,都要卡死一会,查了一天,最终new thread解决,这个页面打开过好几次,最后一次注意到了开线程的方式。