Проблема с использованием потоков пулов заключается в том, что они проводят большую часть своего времени, ожидая ответа с веб-сайта. И проблема с использованием Parallel.ForEach
заключается в том, что он ограничивает ваш параллелизм.
У меня лучшая производительность при использовании асинхронных веб-запросов. Я использовал Semaphore
, чтобы ограничить количество одновременных запросов, а функция обратного вызова выполнила очистку.
Основной поток создает 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 выполняется в вызывающем потоке (в этом случае основной поток). См. Is this really asynchronous? для получения дополнительной информации.
Это прост в применении и работает достаточно хорошо. Можно получить еще более 20 одновременных запросов, но, по моему опыту, это требует немалых усилий. Мне пришлось делать много кэширования DNS и ... ну, это было сложно.
Возможно, вы, возможно, упростите это, используя Task
и новый асинхронный материал в C# 5.0 (.NET 4.5). Я не достаточно знаком с тем, чтобы сказать, как это сделать.
Почему бы не использовать 'Parallel.For' или' TaskFactory.StartNew'? –