使用池线程的问题在于它们大部分时间都在等待来自 Web 站点的响应。使用的问题Parallel.ForEach
是它限制了你的并行性。
通过使用异步 Web 请求,我获得了最佳性能。我用 aSemaphore
来限制并发请求的数量,回调函数做了抓取。
主线程创建Semaphore
,如下所示:
Semaphore _requestsSemaphore = new Semaphore(20, 20);
20
是通过反复试验得出的。事实证明,限制因素是 DNS 解析,平均而言,它需要大约 50 毫秒。至少,在我的环境中确实如此。20 个并发请求是绝对最大值。15可能更合理。
主线程本质上是循环的,如下所示:
while (true)
{
_requestsSemaphore.WaitOne();
string urlToCrawl = DequeueUrl(); // however you do that
var request = (HttpWebRequest)WebRequest.Create(urlToCrawl);
// set request properties as appropriate
// and then do an asynchronous request
request.BeginGetResponse(ResponseCallback, request);
}
该ResponseCallback
方法将在池线程上调用,执行处理,处理响应,然后释放信号量以便可以发出另一个请求。
void ResponseCallback(IAsyncResult ir)
{
try
{
var request = (HttpWebRequest)ir.AsyncState;
// you'll want exception handling here
using (var response = (HttpWebResponse)request.EndGetResponse(ir))
{
// process the response here.
}
}
finally
{
// release the semaphore so that another request can be made
_requestSemaphore.Release();
}
}
正如我所说,限制因素是 DNS 解析。事实证明,DNS 解析是在调用线程(本例中为主线程)上完成的。请参阅这真的是异步的吗?了解更多信息。
这很容易实现并且效果很好。有可能获得超过 20 个并发请求,但根据我的经验,这样做需要相当多的努力。我不得不做很多 DNS 缓存,而且……嗯,这很困难。
您可能可以通过使用Task
C# 5.0 (.NET 4.5) 中的新异步内容来简化上述内容。不过,我对那些说的不太熟悉。