Рассмотрим такую проблему: у меня есть программа, которая должна получить (допустим) 100 записей из базы данных, а затем для каждой из них получить обновленную информацию из веб-сервиса. Есть два способа внедрить параллелизм в этот сценарий:
Я запускаю каждый запрос к веб-сервису на новом потоке. Количество одновременных потоков контролируется каким-то внешним параметром (или динамически настраивается каким-то образом).
Я создаю более мелкие партии (допустим, по 10 записей) и запускаю каждую партию в отдельном потоке (в нашем примере - 10 потоков).
Какой подход лучше, и почему вы так думаете?
Выбор 3 является лучшим:
Используйте Async IO.
Если Ваша обработка запроса не сложна и тяжела, Ваша программа собирается потратить 99% it' s время, ожидая запросов HTTP.
Это точно, что Async IO разработан для - Позволяют стопке организации сети окон (или .net структура или безотносительно) беспокойство обо всем ожидании, и просто используют единственную нить, чтобы послать и ' выберите up' результаты.
К сожалению.NET структура делает его правильной болью в заднице. It' s легче, если you' ре, просто используя сырые гнезда или Win32 api. Here' s (проверенный!) пример, используя C#3 так или иначе:
using System.Net; // need this somewhere
// need to declare an class so we can cast our state object back out
class RequestState {
public WebRequest Request { get; set; }
}
static void Main( string[] args ) {
// stupid cast neccessary to create the request
HttpWebRequest request = WebRequest.Create( "http://www.stackoverflow.com" ) as HttpWebRequest;
request.BeginGetResponse(
/* callback to be invoked when finished */
(asyncResult) => {
// fetch the request object out of the AsyncState
var state = (RequestState)asyncResult.AsyncState;
var webResponse = state.Request.EndGetResponse( asyncResult ) as HttpWebResponse;
// there we go;
Debug.Assert( webResponse.StatusCode == HttpStatusCode.OK );
Console.WriteLine( "Got Response from server:" + webResponse.Server );
},
/* pass the request through to our callback */
new RequestState { Request = request }
);
// blah
Console.WriteLine( "Waiting for response. Press a key to quit" );
Console.ReadKey();
}
ОТРЕДАКТИРУЙТЕ:
В случае.NET, ' завершение callback' на самом деле запущен в нить ThreadPool, не в Вашу главную нить, таким образом, Вы должны будете все еще захватить любые общие ресурсы, но она все еще экономит Вам всю проблему управлять нитями.
Два момента, которые необходимо учитывать.
Если обработка записи происходит очень быстро, накладные расходы на передачу записей потокам могут стать узким местом. В этом случае вы захотите объединить записи, чтобы не передавать их так часто.
Если обработка записей достаточно длительная, разница будет незначительной, поэтому более простой подход (1 запись на поток), вероятно, будет лучшим.
Если вы не используете пул потоков, я думаю, вам нужно либо вручную ограничить количество потоков, либо разбить данные на большие куски. Запуск нового потока для каждой записи приведет к аварийному завершению работы системы, если количество записей станет большим.
Компьютер, на котором выполняется программа, вероятно, не является узким местом: Помните, что протокол HTTP имеет заголовок keep-alive, который позволяет посылать несколько GET-запросов на одном и том же сокете, что избавляет вас от рукопожатия TCP/IP. К сожалению, я не знаю, как использовать это в библиотеках .net. (Должно быть возможно.)
Вероятно, также будет задержка в ответе на ваши запросы. Вы можете попробовать сделать так, чтобы у вас всегда было заданное количество невыполненных запросов к серверу.
Получите Parallel Fx. Посмотрите на коллекцию BlockingCollection. Используйте поток для подачи в нее партий записей, и от 1 до n потоков, извлекающих записи из коллекции для обслуживания. Вы можете контролировать скорость подачи коллекции и количество потоков, которые обращаются к веб-сервисам. Сделайте его настраиваемым с помощью ConfigSection и сделайте его общим, подавая коллекцию делегатам Action, и у вас будет хороший маленький дозатор, который вы сможете использовать по своему усмотрению.