// 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);
});
Aqui está o problema, ele inicia mais de 1000 solicitações da web simultâneas. Existe uma maneira fácil de limitar a quantidade simultânea dessas solicitações http assíncronas? Para que não mais do que 20 páginas da web sejam baixadas a qualquer momento. Como fazer da maneira mais eficiente?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Grief Coder
fonte
fonte
HttpClient
éIDisposable
, e você deve descartá-lo, especialmente quando for usar mais de 1000 deles.HttpClient
pode ser usado como um singleton para várias solicitações.Respostas:
Definitivamente, você pode fazer isso nas versões mais recentes do async para .NET, usando .NET 4.5 Beta. O post anterior de 'usr' aponta para um bom artigo escrito por Stephen Toub, mas a notícia menos anunciada é que o semáforo assíncrono realmente chegou à versão Beta do .NET 4.5
Se você olhar para nossa amada
SemaphoreSlim
classe (que você deveria usar, pois tem mais desempenho do que a originalSemaphore
), ela agora possui umaWaitAsync(...)
série de sobrecargas, com todos os argumentos esperados - intervalos de tempo limite, tokens de cancelamento, todos os seus amigos de programação usuais: )Stephen também escreveu uma postagem no blog mais recente sobre os novos recursos do .NET 4.5 que saíram com o beta, consulte Novidades para paralelismo no .NET 4.5 Beta .
Por último, aqui está um exemplo de código sobre como usar SemaphoreSlim para limitação de método assíncrono:
Por último, mas provavelmente uma menção digna é uma solução que usa agendamento baseado em TPL. Você pode criar tarefas vinculadas a delegados no TPL que ainda não foram iniciadas e permitir que um agendador de tarefas personalizado limite a simultaneidade. Na verdade, há uma amostra do MSDN aqui:
Veja também TaskScheduler .
fonte
HttpClient
Parallel.ForEach
funciona com código síncrono. Isso permite que você chame o código assíncrono.IDisposable
susing
outry-finally
declarações e garantir a sua eliminação.Se você tiver um IEnumerable (ou seja, strings de URL s) e quiser fazer uma operação de ligação de E / S com cada um deles (ou seja, fazer uma solicitação assíncrona de http) simultaneamente E, opcionalmente, também deseja definir o número máximo de simultâneos Solicitações de E / S em tempo real, veja como você pode fazer isso. Dessa forma, você não usa pool de threads e outros, o método usa semáforoslim para controlar o máximo de solicitações de E / S simultâneas, semelhante a um padrão de janela deslizante que uma solicitação completa, deixa o semáforo e a próxima entra.
uso: await ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
fonte
using
seria bom.Infelizmente, o .NET Framework está perdendo os combinadores mais importantes para orquestrar tarefas assíncronas paralelas. Não existe tal coisa embutida.
Veja a classe AsyncSemaphore construída pelo mais respeitável Stephen Toub. O que você quer é chamado de semáforo e precisa de uma versão assíncrona dele.
fonte
Existem muitas armadilhas e o uso direto de um semáforo pode ser complicado em casos de erro, então eu sugeriria usar o Pacote NuGet AsyncEnumerator em vez de reinventar a roda:
fonte
O exemplo de Theo Yaung é bom, mas há uma variante sem lista de tarefas em espera.
fonte
ProccessUrl
ou suas subfunções serão realmente ignoradas. Eles serão capturados no Tarefas, mas não filtrados de volta para o chamador original deCheck(...)
. Pessoalmente, é por isso que ainda uso o Tasks e suas funções combinadoras comoWhenAll
eWhenAny
- para obter uma melhor propagação de erros. :)O SemaphoreSlim pode ser muito útil aqui. Aqui está o método de extensão que criei.
Uso de amostra:
fonte
Velha pergunta, nova resposta. @vitidev tinha um bloco de código que foi reutilizado quase intacto em um projeto que analisei. Depois de discutir com alguns colegas, um perguntou "Por que você simplesmente não usa os métodos TPL embutidos?" ActionBlock parece ser o vencedor. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Provavelmente não acabará mudando nenhum código existente, mas definitivamente tentará adotar esse nuget e reutilizar a prática recomendada do Sr. Softy para paralelismo limitado.
fonte
Aqui está uma solução que tira proveito da natureza preguiçosa do LINQ. É funcionalmente equivalente à resposta aceita ), mas usa tarefas de trabalho em vez de a
SemaphoreSlim
, reduzindo assim o consumo de memória de toda a operação. A princípio vamos fazer funcionar sem estrangulamento. O primeiro passo é converter nossos urls em uma lista de tarefas.A segunda etapa é fazer
await
todas as tarefas simultaneamente usando oTask.WhenAll
método:Resultado:
A implementação de Microsoft
Task.WhenAll
materializa instantaneamente o enumerável fornecido a um array, fazendo com que todas as tarefas sejam iniciadas de uma vez. Não queremos isso, porque queremos limitar o número de operações assíncronas simultâneas. Portanto, precisaremos implementar uma alternativaWhenAll
que enumerará nosso enumerável suave e lentamente. Faremos isso criando uma série de tarefas de trabalho (igual ao nível desejado de simultaneidade), e cada tarefa de trabalho irá enumerar nossa tarefa enumerável por vez, usando um bloqueio para garantir que cada tarefa de url será processada por apenas um trabalhador-tarefa. Em seguida, concluímosawait
todas as tarefas do trabalhador e, finalmente, retornamos os resultados. Aqui está a implementação:... e aqui está o que devemos mudar em nosso código inicial, para atingir a limitação desejada:
Há uma diferença quanto ao tratamento das exceções. O nativo
Task.WhenAll
espera que todas as tarefas sejam concluídas e agrega todas as exceções. A implementação acima termina imediatamente após a conclusão da primeira tarefa com falha.fonte
IAsyncEnumerable<T>
pode ser encontrada aqui .Embora 1000 tarefas possam ser enfileiradas muito rapidamente, a biblioteca de Tarefas Paralelas só pode lidar com tarefas simultâneas iguais à quantidade de núcleos de CPU na máquina. Isso significa que se você tiver uma máquina de quatro núcleos, apenas 4 tarefas serão executadas em um determinado momento (a menos que você diminua o MaxDegreeOfParallelism).
fonte
await
palavra - chave lá. Remover isso deve resolver o problema, correto?Running
status) simultaneamente do que a quantidade de núcleos. Isso será especialmente o caso com tarefas vinculadas a E / S.Cálculos paralelos devem ser usados para acelerar as operações vinculadas à CPU. Aqui, estamos falando sobre operações vinculadas a E / S. Sua implementação deve ser puramente assíncrona , a menos que você esteja sobrecarregando o único núcleo ocupado em sua CPU multi-core.
EDITAR Eu gosto da sugestão feita por usr para usar um "semáforo assíncrono" aqui.
fonte
Use
MaxDegreeOfParallelism
, que é uma opção que você pode especificar emParallel.ForEach()
:fonte
GetStringAsync(url)
deve ser chamado comawait
. Se você inspecionar o tipo devar html
, é aTask<string>
, não o resultadostring
.Parallel.ForEach(...)
destina-se à execução de blocos de código síncrono em paralelo (por exemplo, em threads diferentes).Essencialmente, você vai querer criar uma ação ou tarefa para cada URL que deseja acessar, colocá-los em uma lista e, em seguida, processar essa lista, limitando o número que pode ser processado em paralelo.
Minha postagem do blog mostra como fazer isso com Tarefas e com Ações e fornece um projeto de amostra que você pode baixar e executar para ver os dois em ação.
Com ações
Se estiver usando Actions, você pode usar a função integrada .Net Parallel.Invoke. Aqui, limitamos a execução de no máximo 20 threads em paralelo.
Com Tarefas
Com o Tarefas, não há função incorporada. No entanto, você pode usar o que eu disponibilizo no meu blog.
E, em seguida, criar sua lista de tarefas e chamar a função para executá-las, digamos, no máximo 20 simultâneas por vez, você pode fazer o seguinte:
fonte
esta não é uma boa prática, pois altera uma variável global. também não é uma solução geral para assíncrono. mas é fácil para todas as instâncias de HttpClient, se é isso que você está procurando. você pode simplesmente tentar:
fonte