// let's say there is a list of 1000+ URLs
string[] urls = { "http://google.com", "http://yahoo.com", ... };
// now let's send HTTP requests to each of these URLs in parallel
urls.AsParallel().ForAll(async (url) => {
var client = new HttpClient();
var html = await client.GetStringAsync(url);
});
Проблема в том, что он запускает более 1000 одновременных веб-запросов. Есть ли простой способ ограничить одновременное количество этих асинхронных HTTP-запросов? Таким образом, в любой момент времени загружается не более 20 веб-страниц. Как сделать это наиболее эффективно?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Кодер скорби
источник
источник
HttpClient
естьIDisposable
, и вы должны избавиться от него, особенно если вы собираетесь использовать более 1000 штук.HttpClient
может использоваться как синглтон для нескольких запросов.Ответы:
Вы определенно можете сделать это в последних версиях async для .NET, используя .NET 4.5 Beta. Предыдущий пост от 'usr' указывает на хорошую статью, написанную Стивеном Тубом, но менее анонсированная новость заключается в том, что семафор async фактически попал в бета-версию .NET 4.5.
Если вы посмотрите на наш любимый
SemaphoreSlim
класс (который вам следует использовать, поскольку он более производительный, чем исходныйSemaphore
), теперь он может похвастатьсяWaitAsync(...)
серией перегрузок со всеми ожидаемыми аргументами - интервалами тайм-аута, токенами отмены, всеми вашими обычными друзьями по планированию: )Стивен также написал в своем недавнем блоге сообщение о новых возможностях .NET 4.5, которые появились в бета-версии, см. Что нового для параллелизма в .NET 4.5 Beta .
Наконец, вот несколько примеров кода о том, как использовать SemaphoreSlim для регулирования асинхронного метода:
И последнее, но, вероятно, заслуживающее упоминания - решение, использующее планирование на основе TPL. Вы можете создавать задачи с привязкой к делегатам в TPL, которые еще не были запущены, и разрешить настраиваемому планировщику задач ограничивать параллелизм. Фактически, здесь есть образец MSDN:
См. Также TaskScheduler .
источник
HttpClient
Parallel.ForEach
работает с синхронным кодом. Это позволяет вызывать асинхронный код.IDisposable
с вusing
илиtry-finally
заявлениях, а также обеспечить их утилизацию.Если у вас есть IEnumerable (например, строки URL-адресов), и вы хотите выполнить операцию ввода-вывода с каждым из них (например, выполнить асинхронный HTTP-запрос) одновременно И, возможно, вы также хотите установить максимальное количество одновременных Запросы ввода-вывода в реальном времени. Вот как это можно сделать. Таким образом, вы не используете пул потоков и др., Метод использует semaphoreslim для управления максимальным количеством одновременных запросов ввода-вывода, аналогичных шаблону скользящего окна, один запрос завершает, оставляет семафор, а следующий входит.
использование: await ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
источник
using
бы неплохо.К сожалению, в .NET Framework отсутствуют наиболее важные комбинаторы для организации параллельных асинхронных задач. Встроенного такого нет.
Взгляните на класс AsyncSemaphore, созданный самым уважаемым Стивеном Тубом. То, что вам нужно, называется семафором, и вам нужна его асинхронная версия.
источник
Есть много подводных камней, и прямое использование семафора может быть непростым в случае ошибок, поэтому я бы предложил использовать пакет AsyncEnumerator NuGet вместо того, чтобы заново изобретать колесо:
источник
Пример Тео Яунга хорош, но есть вариант без списка ожидающих задач.
источник
ProccessUrl
или его подфункциях, фактически игнорируются. Они будут записаны в Задачи, но не будут возвращены исходному вызывающему объектуCheck(...)
. Лично я до сих пор использую Задачи и их комбинаторные функции, такие какWhenAll
иWhenAny
- чтобы улучшить распространение ошибок. :)SemaphoreSlim может быть здесь очень полезным. Вот созданный мной метод расширения.
Пример использования:
источник
Старый вопрос, новый ответ. У @vitidev был блок кода, который был повторно использован почти без изменений в проекте, который я рассмотрел. После обсуждения с несколькими коллегами один спросил: «Почему бы вам просто не использовать встроенные методы TPL?» ActionBlock здесь выглядит победителем. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Вероятно, в конечном итоге не изменится какой-либо существующий код, но определенно будет стремиться принять этот nuget и повторно использовать лучшие практики г-на Софти для дросселирования параллелизма.
источник
Вот решение, использующее ленивую природу LINQ. Он функционально эквивалентен принятому ответу ), но использует рабочие задачи вместо a
SemaphoreSlim
, уменьшая таким образом объем памяти, занимаемый всей операцией. Сначала заставим работать без троттлинга. Первый шаг - преобразовать наши URL-адреса в множество задач.Второй шаг - выполнить
await
все задачи одновременно с использованиемTask.WhenAll
метода:Вывод:
Реализация Microsoft, из
Task.WhenAll
материализуешься мгновенно поставляется перечислима в массив, в результате чего все задачи на старты сразу. Нам этого не нужно, потому что мы хотим ограничить количество одновременных асинхронных операций. Итак, нам нужно реализовать альтернативу,WhenAll
которая будет мягко и медленно перечислять наши перечисления. Мы сделаем это, создав несколько рабочих задач (равных желаемому уровню параллелизма), и каждая рабочая задача будет перечислять наши перечисляемые задачи по одной за раз, используя блокировку, чтобы гарантировать, что каждая задача url будет обработана. всего одним рабочим заданием. Затем мыawait
завершаем все рабочие задачи и, наконец, возвращаем результаты. Вот реализация:... и вот что мы должны изменить в нашем исходном коде, чтобы добиться желаемого регулирования:
Есть разница в обработке исключений. Собственный код
Task.WhenAll
ожидает завершения всех задач и собирает все исключения. Вышеупомянутая реализация завершается сразу же после завершения первой неисправной задачи.источник
IAsyncEnumerable<T>
можно найти здесь .Хотя 1000 задач могут быть поставлены в очередь очень быстро, библиотека Parallel Tasks может обрабатывать только параллельные задачи, равные количеству ядер ЦП на машине. Это означает, что если у вас четырехъядерный компьютер, только 4 задачи будут выполняться одновременно (если вы не снизите MaxDegreeOfParallelism).
источник
await
ключевого слова. Удаление должно решить проблему, верно?Running
статусом) одновременно, чем количество ядер. Это будет особенно актуально для задач, связанных с вводом-выводом.Параллельные вычисления следует использовать для ускорения операций, связанных с процессором. Здесь мы говорим о связанных операциях ввода-вывода. Ваша реализация должна быть чисто асинхронной , если только вы не перегружаете загруженное одно ядро вашего многоядерного процессора.
ИЗМЕНИТЬ Мне нравится предложение usr использовать здесь «асинхронный семафор».
источник
Использование
MaxDegreeOfParallelism
, которое можно указать вParallel.ForEach()
:источник
GetStringAsync(url)
предназначен для вызова с помощьюawait
. Если вы посмотрите на типvar html
, то это будет, аTask<string>
не результатstring
.Parallel.ForEach(...)
предназначен для параллельного выполнения блоков синхронного кода (например, в разных потоках).По сути, вам нужно создать действие или задачу для каждого URL-адреса, по которому вы хотите попасть, поместить их в список, а затем обработать этот список, ограничив количество, которое может обрабатываться параллельно.
В моем сообщении в блоге показано, как сделать это как с помощью задач, так и с помощью действий, и представлен образец проекта, который вы можете загрузить и запустить, чтобы увидеть оба в действии.
С Действиями
Если вы используете Actions, вы можете использовать встроенную функцию .Net Parallel.Invoke. Здесь мы ограничиваем его параллельным запуском не более 20 потоков.
С задачами
В задачах нет встроенной функции. Однако вы можете использовать тот, который я предоставил в своем блоге.
А затем, создав список задач и вызвав функцию для их запуска, скажем, не более 20 одновременно, вы можете сделать это:
источник
это не лучшая практика, поскольку она изменяет глобальную переменную. это также не общее решение для async. но это легко для всех экземпляров HttpClient, если это все, что вам нужно. вы можете просто попробовать:
источник