使用线程池线程的问题是,他们花费大部分时间等待Web站点的响应。而使用Parallel.ForEach
的问题是它限制了你的并行性。
我通过使用异步Web请求获得了最佳性能。我使用了Semaphore
来限制并发请求的数量,并且回调函数进行了修改。
主线程创建Semaphore
,就像这样:
Semaphore _requestsSemaphore = new Semaphore(20, 20);
的20
是通过试验和错误的。事实证明,限制因素是DNS解析,平均而言,大约需要50 ms。至少,它在我的环境中。 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解析是在调用线程(在这种情况下是主线程)完成的。有关更多信息,请参见Is this really asynchronous?。
这很容易实现,效果很好。根据我的经验,有可能获得甚至超过20个并发请求,但这样做需要相当多的努力。我不得不做很多DNS缓存,呃,这很困难。
您可以通过使用Task
和C#5.0(.NET 4.5)中的新异步内容来简化上述操作。尽管如此,我对这些人不太熟悉。
为什么不使用'Parallel.For'或'TaskFactory.StartNew'? – 2013-04-20 00:42:43