好了,这将是一篇比较长的贴子。本人也是第一次发文。 Sufei是个好人,本人写爬虫用到过Sufei的类,大赞一个。
之前有一篇帖子http://www.sufeinet.com/thread-8352-1-1.html 讨论异步的实现,好了,这个讨论完全不着边际。既然曾经收益于HttpHelper,特来回报下站长Sufei。
我们都知道,如果同步请求Internet数据,会造成线程阻塞,实战中,如果开大量线程,会造成CPU线程上下文切换频繁,影响执行效率,同时,越多的线程,意味着GC回收时挂起的线程就越多,效率就越低。于是,一个异步的IO请求就成了追求完美的码农的解决方案。
.net中的异步实现有两种方式,一种为APM,一种为EAP,如果不明白的请自己Google好了,这里不详细介绍。我这里给出APM的实现。
好了 直接说下实现过程,这个不复杂,MSDN上有,稍微改造一下就行http://msdn.microsoft.com/zh-cn/library/86wf6409(v=vs.110).aspx,
再借鉴SuFei的类,一个异步实现的HttpHelper就完成了
献丑了,贴上改造HttpHelper后的代码(大家发现没有的方法,在HttpHelper都有,发帖长度被限制,只能干掉了)
[C#] 纯文本查看 复制代码 #define trace
//#undef trace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Spider.Tool;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using Spider.DataControl.SpiderTrace;
namespace Spider.DataControl.Tool
{
sealed class [color=#ff0000]MyAsyncPara[/color]
{
[color=#ff0000] public const int DefaultTimeOutSpan = 60 * 1000;[/color]
public HttpResult result = new HttpResult();
//HttpWebRequest对象用来发起请求
public HttpWebRequest request = null;
//获取影响流的数据对象
public HttpWebResponse response = null;
//响应流对象
public Stream streamResponse;
public int Length = 10 * 1024;
public Byte[] buffer = new Byte[10 * 1024];
public Action<HttpResult> callBack;
public MemoryStream MemoryStream = new MemoryStream();
public HttpItem objhttpItem;
public bool TimeOut = false;
}
public class AsyncHttpHelper : IDisposable
{
#region 预定义方法或者变更
public void GetHtmlDataAsync(HttpItem objhttpItem, [color=#ff0000]Action<HttpResult> callBack[/color])
{
MyAsyncPara pa = new MyAsyncPara();
#if trace
[color=#ff0000] TraceDic.AddAsyncUrl(objhttpItem.urlGuid, objhttpItem.URL);[/color]
pa.result.Guid = objhttpItem.urlGuid;
#endif
pa.objhttpItem = objhttpItem;
pa.callBack = callBack;
try
{
SetRequest(pa);
pa.request.BeginGetResponse(GetResponseCallback, pa);
}
catch (Exception ex)
{
pa.result.Html = "0";
pa.result.ErrorMsg = ex.Message;
callBack(pa.result);
}
}
void GetResponseCallback(IAsyncResult result)
{
MyAsyncPara pa = result.AsyncState as MyAsyncPara;
try
{
pa.response = (HttpWebResponse)pa.request.EndGetResponse(result);
if (pa.response.ContentEncoding != null && pa.response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
{
//开始读取流并设置编码方式
//new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240);
//.net4.0以下写法
pa.streamResponse = new GZipStream(pa.response.GetResponseStream(), CompressionMode.Decompress);
}
else
{
//开始读取流并设置编码方式
//response.GetResponseStream().CopyTo(_stream, 10240);
//.net4.0以下写法
pa.streamResponse = pa.response.GetResponseStream();
}
pa.streamResponse.BeginRead(pa.buffer, 0, pa.Length, ReadResponseStreamCallBack, pa);
}
catch (Exception ex)
{
pa.result.ErrorMsg = ex.Message;
pa.result.Html = "0";
pa.callBack(pa.result);
}
}
private void [color=#ff0000]TimeoutCallback(object state, bool timedOut)[/color]
{
if (timedOut)
{
System.Diagnostics.Trace.WriteLine("timeout");
MyAsyncPara pa = state as MyAsyncPara;
pa.TimeOut = true;
ProcessData(pa);
}
}
void ReadResponseStreamCallBack(IAsyncResult result)
{
MyAsyncPara pa = result.AsyncState as MyAsyncPara;
if (pa.TimeOut)
return;
try
{
int bytesRead = pa.streamResponse.EndRead(result);
if (bytesRead > 0)
{
pa.MemoryStream.Write(pa.buffer, 0, bytesRead);
System.Threading.Thread.Yield();
IAsyncResult ar = pa.streamResponse.BeginRead(pa.buffer, 0, pa.Length, ReadResponseStreamCallBack, pa);
[color=#ff0000] System.Threading.ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, TimeoutCallback, pa, MyAsyncPara.DefaultTimeOutSpan, true);[/color]
}
else
{
ProcessData(pa);
}
}
catch (Exception ex)
{
pa.result.ErrorMsg = ex.Message;
pa.result.Html = "0";
pa.callBack(pa.result);
}
}
private void ProcessData(MyAsyncPara pa)
{
byte[] RawResponse = pa.MemoryStream.ToArray();
//..此处省略三百字
pa.MemoryStream.Close();
pa.response.Close();
pa.request.Abort();
pa.callBack(pa.result);
}
}
}
好了,这里面的HttpItem和HttpResult都是站长Sufei的HttpHelper提供的。大家如果没有自行下载。
这个类的用法很简单,如下。
[C#] 纯文本查看 复制代码 AsyncHttpHelper target = new AsyncHttpHelper();
HttpItem objhttpItem = new HttpItem();
objhttpItem.URL = "http://myhb.qq.com/f-1001229421-1.htm";
[color=#ff0000] objhttpItem.urlGuid = Guid.NewGuid().ToString();[/color]
target.GetHtmlDataAsync(objhttpItem, [color=#ff0000]CallBack[/color]);
至于CallBacK方法,是个匿名委托,就不多介绍了,用来处理请求的结果。
好了,细心的童鞋可能发现了,我用了一个类,用来追踪异步请求时的Url ,这个类的实现很简单,如下:
[C#] 纯文本查看 复制代码 public class TraceDic
{
private static Dictionary<string, string> urlDic = new Dictionary<string, string>();
private static object m_lock = new object();
public static void AddAsyncUrl(string guid, string url)
{
lock (m_lock)
{
urlDic.Add(guid, DateTime.Now.ToString() + " " + url);
}
}
public static void RemoveUrl(string guid)
{
lock (m_lock)
{
urlDic.Remove(guid);
}
}
public static string GetString()
{
lock (m_lock)
{
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, string> s in urlDic)
{
if (string.IsNullOrEmpty(sb.ToString()))
sb.Append("\r\n");
sb.Append(s.Key + " " + s.Value + "\r\n");
}
return sb.ToString();
}
}
}
好了,为什么要干这件事情呢,先埋个坑。
童鞋们会发现,以前需要开N个线程去请求N个链接,或者放入线程池,与前者等价。现在用我的异步类,加上一个队列,一个线程就可以轻松愉快的解决了,完全不会阻塞IO,速度飞快,等请求完了之后,系统会自动调用你的委托方法。由于线程变少了,CPU切换变少了,GC回收效率变高了,整个程序一下子就清爽了很多。
再细心点的童鞋会问,
System.Threading.ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, TimeoutCallback, pa, MyAsyncPara.DefaultTimeOutSpan, true);
这段代码是干嘛的?
好了,简单点说,就是设定一个异步超时值,如果超时,那么不再等待,直接在TimeOutCallBack中,处理结果了。
那么,现在这个类看起来没什么问题了。欢迎大家使用。
如果有填坑的,欢迎跟帖。
|