.net IO异步和Producer/Consumer队列实现一分钟n次http请求
简介
最近工作中有一个需求:要求发送http请求到某站点获取相应的数据,但对方网站限制了请求的次数:一分钟最多200次请求。
搜索之后,在stackoverflow网站查到一个类似的问题.。但里面用到了Reactive Extensions,权衡之下最后还是决定自己简单实现一分钟最多200次请求。
思路
思路很简单,一分钟200次,平均下来一次请求300ms,大概3次的时候将近一秒,所以一次异步发送三个请求,然后线程暂停900ms。
这里的关键是运行代码时尽量不要堵塞线程,可以速度很快执行发送请求之前的代码。
实现
异步请求
http请求属于IO请求,其异步可以调用HttpWebRequest.BeginGetResponse方法实现,但现在流行TPL,方法TaskFactory.FromAsync更加方便简介。
request = (HttpWebRequest)WebRequest.Create(addUrl);
request.Method = "POST";
request.Timeout = timeOut;
request.Proxy = null;
request.Accept = "application/xml, */*";
request.ContentType = "application/xml"; XElement inputElem = BuildRequestInputXml(userName, pwd, ctripPolicy);
byte[] inputBytes = Encoding.UTF8.GetBytes(inputStr);
inputBytes = ms.ToArray();
using (var stream = request.GetRequestStream())
{
stream.Write(inputBytes, 0, inputBytes.Length);
}
Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null, TaskCreationOptions.None).ContinueWith(
t =>
{
HttpWebResponse response = t.Result;
using (StreamReader reader = new StreamReader(responseStream))
{
responseStr = reader.ReadToEnd();
}
DBAccessHelper.UpdateDB(responseStr); });
异步实现之后就是发送三次请求,然后暂停900ms:
for (int i = 0; i < datas.Length; i++)
{
StartOneQueryAsync(datas[i].t1, datas[i].t2); if ((i + 1) % 3 == 0)
{
Thread.Sleep(TimeSpan.FromMilliseconds(900));
}
}
测试和改进
简单实现之后就开始测试,但后来发现代码在发送了200次请求之后,后续请求就会被堵塞很长的时间(3~4s),最后测试结果是4000个请求大概30分钟才完成,这个和我们理想的20分钟有很大的差距。
一开始的分析是发送请求次数过多,因为是异步发送,后续处理的线程可能不够而导致线程被堵塞。
这里的解决方法就是使用 生产者/消费者队列,如网上的MSMQ,RabbitMQ等。
不过在.net 4.0中添加了一些异步集合类:ConcurrentStack<T>,ConcurrentQueue<T>,ConcurrentBag<T>,ConcurrentDictionary<TKey,TValue>。
所以这里的思路就是用异步队列ConcurrentQueue<Action>将要执行的方法Action添加到异步队列中,然后开启2到3个格外的线程从异步队列中获取Action再执行之。
这种ProducerConsumer模式在.net 4.0中也已存在,其中有BlockingCollection<T>类就实现了IProducerConsumerCollection<T>接口,有了这些之后我们就可以实现一个Producer/Consumer 队列:
public class UpdateDBQueue : IDisposable
{
BlockingCollection<Action> _taskQ = new BlockingCollection<Action>(); public UpdateDBQueue(int workerCount)
{
// 创建格外的线程来执行task
for (int i = 0; i < workerCount; i++)
{
Task.Factory.StartNew(Consume);
}
} public void Enqueue(Action action) { _taskQ.Add(action); } void Consume()
{
// 队列中没有数据就会被堵塞,在方法CompleteAdding被调用之后就会自动结束
foreach (Action action in _taskQ.GetConsumingEnumerable())
{
action(); // Perform task.
}
} public void Dispose()
{
_taskQ.CompleteAdding();
}
}
这里如果还是 .net 2.0的同学可以参考stackoverflow的这篇文章,里面有介绍2.0如何实现Producer/Consumer 队列
下面就是新加了队列后的代码。
Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null, TaskCreationOptions.None).ContinueWith(
t =>
{
HttpWebResponse response = t.Result;
using (StreamReader reader = new StreamReader(responseStream))
{
responseStr = reader.ReadToEnd();
}
//数据更新推送到队列
updateUBQueue.Enqueue(() =>{
DBAccessHelper.UpdateDB(responseStr);
});
});
持续测试和改进
加上队列之后再次测试,发现在200次请求之后还是有堵塞的情况发生,这样看样子应该不是后续处理线程不够,应该是请求的时候线程被堵塞。
代码比较简单,后来发现request.GetRequestStream方法也有对应的HttpWebRequest.GetRequestStream,看样子也是IO请求,所以最后也写成异步:
Task.Factory.FromAsync<Stream>(request.BeginGetRequestStream, request.EndGetRequestStream, null, TaskCreationOptions.None)
.ContinueWith(streamTask =>
{
using (var stream = streamTask.Result)
{
stream.Write(inputBytes, 0, inputBytes.Length);
} Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null, TaskCreationOptions.None)
.ContinueWith(responseTask =>
{
#region Get Response
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)responseTask.Result;
string responseStr = string.Empty;
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(responseStream))
{
responseStr = reader.ReadToEnd();
}
}
updateUBQueue.Enqueue(() =>
{
DBAccessHelper.UpdateDB(responseStr);
});
}
catch (Exception ex)
{
LogHelper.Log("*****", ex);
}
finally
{
if (response != null)
{
response.Close();
}
if (request != null)
{
request.Abort();
}
} }); });
最后测试的时候发现4000多个请求在21分钟就可以完成,测试通过。
总结:
http异步请求的时候GetResponse也需要异步,.net 4.0已经包含了异步队列ConcurrentQueue<T>,使用BlockingCollection<T>可以实现自己的Producer/Consumer 队列。
.net IO异步和Producer/Consumer队列实现一分钟n次http请求的更多相关文章
- 【python】-- 事件驱动介绍、阻塞IO, 非阻塞IO, 同步IO,异步IO介绍
事件驱动介绍 一.前言 通常,我们写服务器处理模型的程序时,有以下几种模型: (1)每收到一个请求,创建一个新的进程,来处理该请求: (2)每收到一个请求,创建一个新的线程,来处理该请求: (3)每收 ...
- Python学习-day10(番外篇) 阻塞IO 非阻塞IO 同步IO 异步IO
这个章节的内容是关于IO的概念,谈一谈什么是 阻塞IO 非阻塞IO 同步IO 异步IO.以下摘要是我对这四种IO的一个形象理解. 场景是去去银行办理业务.节点有三个,1)到银行提交申请:2)取号:3) ...
- 【原】iOS多线程之异步任务+并行队列情况与异步任务+串行队列(主队列)情况
异步任务+并行队列 把异步任务放到并行队列进行执行,异步任务会在不同的线程中执行. /*异步执行+并行队列*/ - (IBAction)clickBasic1:(UIButton *)sender { ...
- IO复用\阻塞IO\非阻塞IO\同步IO\异步IO
转载:IO复用\阻塞IO\非阻塞IO\同步IO\异步IO 一. 什么是IO复用? 它是内核提供的一种同时监控多个文件描述符状态改变的一种能力:例如当进程需要操作多个IO相关描述符时(例如服务器程序要同 ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- 阻塞IO,非阻塞IO,异步IO和非异步IO 的区别
最近在研究java IO.NIO.NIO2(或者称AIO)相关的东西,有些概念还是要明确下. 按照<Unix网络编程>的划分,IO模型可以分为:阻塞IO.非阻塞IO.IO复用.信号驱动IO ...
- C# Producer Consumer (生产者消费者模式)demo
第一套代码将producer Consumer的逻辑写到from类里了,方便在demo的显示界面动态显示模拟生产和消费的过程. 第二套代码将producer Consumer的逻辑单独写到一个 ...
- 阻塞IO, 非阻塞IO, 同步IO,异步IO
阻塞IO, 非阻塞IO, 同步IO,异步IO 介绍 先说明几个概念 用户空间与内核空间 为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间(内存)划分为两部分,一部分 ...
- Kafka 学习笔记之 Kafka0.11之producer/consumer(Scala)
Kafka0.11之producer/consumer(Scala): KafkaConsumer: import java.util.Properties import org.apache.kaf ...
随机推荐
- Java-条件语句、循环语句练习
题目一:一张纸的厚度大约是0.08mm,对折多少次之后能达到珠穆朗玛峰的高度(8848.13米)? double height=0.08; for(int i=1;i>0;i++) { heig ...
- JS实现无限分页加载——原理图解
由于网页的执行都是单线程的,在JS执行的过程中,页面会呈现阻塞状态.因此,如果JS处理的数据量过大,过程复杂,可能会造成页面的卡顿.传统的数据展现都以分页的形式,但是分页的效果并不好,需要用户手动点击 ...
- Redis 监控
redis 监控有 redis-live 和 redis-stat Redis-Live是一个用来监控redis实例,分析查询语句并且有web界面的监控工具,使用python编写. redis-sta ...
- fir.im Weekly - 不能错过的 GitHub Top 100 开源库
好的工具&资源,会带来更多的灵感.本期 fir.im Weekly 精选了一些实用的 iOS,Android 的使用工具和源码分享,还有前端.UI方面的干货.一起来看下:) Swift 开源项 ...
- Python无聊的总结
在公司无聊的时候看了前辈写的python代码,突然发现一个比较好玩的python表达式: lambda x,y:x+y 咋一看,这个应该类似方法之类的,上网查了查,所以特此总结下 lambda:上代码 ...
- javaweb回顾第十一篇过滤器(附实现中文乱码问题)
1:过滤器概念 过滤器就是一种在请求目标资源的中间组件,比喻把污水转换成纯净水中间需要一个污水净化设备,那么这个设备就好比一个过滤器.那么我用图来表示过滤器(可以有多个过滤器)运行的过程 2:Filt ...
- lufylegend游戏引擎
lufylegend游戏引擎介绍:click 这个链接我觉得已经很详细的介绍了这个引擎. 所以以下我只说说一些简单的游戏代码过程. 首先从canvas做游戏叙述起: 这是一个让人很熟悉的简单小游戏,网 ...
- 分享.NET系统开发过程中积累的扩展方法
.NET 3.5提供的扩展方法特性,可以在不修改原类型代码的情况下扩展它的功能.下面分享的这些扩展方法大部分来自于Code Project或是Stackoverflow,.NET为此还有一个专门提供扩 ...
- Android入门(十六)调用摄像头相册
原文链接:http://www.orlion.ga/665/ 一.调用摄像头 创建一个项目ChoosePicDemo,修改activity_main.xml: <LinearLayout xml ...
- HTML5的学习--performance获取加载时间的工具
前段时间因为项目需要获取页面加载的时间,就去看了下HTML5中的performane. 可以用其获得页面详细的加载时间. 关于performance的详细内容可以查看 http://www.cnblo ...