// 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);
});
Voici le problème, il démarre plus de 1000 requêtes Web simultanées. Existe-t-il un moyen simple de limiter le nombre simultané de ces requêtes http asynchrones? Ainsi, pas plus de 20 pages Web ne sont téléchargées à un moment donné. Comment le faire de la manière la plus efficace?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Codeur de chagrin
la source
la source
HttpClient
estIDisposable
, et vous devez disposer, surtout quand vous allez utiliser 1000+ d'entre eux.HttpClient
peut être utilisé comme un singleton pour plusieurs requêtes.Réponses:
Vous pouvez certainement le faire dans les dernières versions d'async pour .NET, en utilisant .NET 4.5 Beta. Le post précédent de 'usr' pointe vers un bon article écrit par Stephen Toub, mais la nouvelle moins annoncée est que le sémaphore asynchrone est en fait entré dans la version bêta de .NET 4.5
Si vous regardez notre
SemaphoreSlim
classe bien - aimée (que vous devriez utiliser car elle est plus performante que l'originalSemaphore
), elle possède désormais laWaitAsync(...)
série de surcharges, avec tous les arguments attendus - intervalles de temporisation, jetons d'annulation, tous vos amis de planification habituels: )Stephen a également écrit un article de blog plus récent sur les nouveaux goodies .NET 4.5 sortis avec la version bêta, voir Quoi de neuf pour le parallélisme dans .NET 4.5 bêta .
Enfin, voici un exemple de code sur l'utilisation de SemaphoreSlim pour la limitation des méthodes asynchrones:
Enfin, mais probablement une mention digne d'être mentionnée, une solution qui utilise la planification basée sur TPL. Vous pouvez créer des tâches liées aux délégués sur le TPL qui n'ont pas encore été démarrées et permettre à un planificateur de tâches personnalisé de limiter la concurrence. En fait, il existe un exemple MSDN pour cela ici:
Voir aussi TaskScheduler .
la source
HttpClient
Parallel.ForEach
fonctionne avec du code synchrone. Cela vous permet d'appeler du code asynchrone.IDisposable
les déclarationsusing
outry-finally
déclarations et d'assurer leur élimination.Si vous avez un IEnumerable (c.-à-d. Des chaînes d'URL) et que vous souhaitez effectuer une opération liée aux E / S avec chacun d'entre eux (c.-à-d. Faire une requête http asynchrone) simultanément ET éventuellement, vous souhaitez également définir le nombre maximal de Demandes d'E / S en temps réel, voici comment vous pouvez le faire. De cette façon, vous n'utilisez pas de pool de threads et autres, la méthode utilise semaphoreslim pour contrôler le nombre maximal de requêtes d'E / S simultanées, similaire à un modèle de fenêtre glissante qu'une requête se termine, quitte le sémaphore et la suivante entre.
utilisation: attendre ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
la source
using
serait bien.Malheureusement, le .NET Framework manque les combinateurs les plus importants pour l'orchestration des tâches asynchrones parallèles. Il n'y a pas une telle chose intégrée.
Regardez la classe AsyncSemaphore construite par le plus respectable Stephen Toub. Ce que vous voulez s'appelle un sémaphore, et vous en avez besoin d'une version asynchrone.
la source
Il y a beaucoup de pièges et l'utilisation directe d'un sémaphore peut être délicate dans les cas d'erreur, donc je suggérerais d'utiliser AsyncEnumerator NuGet Package au lieu de réinventer la roue:
la source
L'exemple de Theo Yaung est sympa, mais il existe une variante sans liste de tâches en attente.
la source
ProccessUrl
ou ses sous-fonctions seront en fait ignorées. Ils seront capturés dans les tâches, mais ne seront pas renvoyés à l'appelant d'origine deCheck(...)
. Personnellement, c'est pourquoi j'utilise toujours les tâches et leurs fonctions de combinateur commeWhenAll
etWhenAny
- pour obtenir une meilleure propagation des erreurs. :)SemaphoreSlim peut être très utile ici. Voici la méthode d'extension que j'ai créée.
Exemple d'utilisation:
la source
Ancienne question, nouvelle réponse. @vitidev avait un bloc de code qui a été réutilisé presque intact dans un projet que j'ai examiné. Après avoir discuté avec quelques collègues, l'un d'eux a demandé "Pourquoi n'utilisez-vous pas simplement les méthodes TPL intégrées?" ActionBlock ressemble au gagnant là-bas. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Probablement ne finira pas par changer de code existant, mais cherchera certainement à adopter ce nuget et à réutiliser les meilleures pratiques de M. Softy pour le parallélisme limité.
la source
Voici une solution qui tire parti de la nature paresseuse de LINQ. Elle est fonctionnellement équivalente à la réponse acceptée ), mais utilise des tâches de travail au lieu de a
SemaphoreSlim
, réduisant ainsi l'empreinte mémoire de l'ensemble de l'opération. Au début, faisons en sorte que cela fonctionne sans étranglement. La première étape consiste à convertir nos URL en une liste de tâches.La deuxième étape consiste à
await
exécuter toutes les tâches simultanément en utilisant laTask.WhenAll
méthode:Production:
L'implémentation de Microsoft
Task.WhenAll
matérialise instantanément l'énumérable fourni dans un tableau, provoquant le démarrage simultané de toutes les tâches. Nous ne voulons pas de cela, car nous voulons limiter le nombre d'opérations asynchrones simultanées. Nous devrons donc implémenter une alternativeWhenAll
qui énumérera notre énumérable doucement et lentement. Nous le ferons en créant un certain nombre de tâches de travail (égal au niveau souhaité de concurrence), et chaque tâche de travail énumérera notre tâche énumérable une tâche à la fois, en utilisant un verrou pour garantir que chaque tâche d'URL sera traitée par une seule tâche de travail. Ensuite, nousawait
pour que toutes les tâches de travail soient terminées, et enfin nous renvoyons les résultats. Voici la mise en œuvre:... et voici ce que nous devons changer dans notre code initial, pour atteindre la limitation souhaitée:
Il y a une différence concernant le traitement des exceptions. Le natif
Task.WhenAll
attend que toutes les tâches soient terminées et regroupe toutes les exceptions. L'implémentation ci-dessus se termine rapidement après l'achèvement de la première tâche défaillante.la source
IAsyncEnumerable<T>
peut être trouvée ici .Bien que 1000 tâches puissent être mises en file d'attente très rapidement, la bibliothèque de tâches parallèles ne peut gérer que des tâches simultanées égales à la quantité de cœurs de processeur de la machine. Cela signifie que si vous avez une machine à quatre cœurs, seules 4 tâches seront exécutées à un moment donné (sauf si vous réduisez le MaxDegreeOfParallelism).
la source
await
mot - clé là-dedans. Supprimer cela devrait résoudre le problème, n'est-ce pas?Running
état) simultanément que la quantité de cœurs. Ce sera particulièrement le cas avec des tâches liées aux E / S.Les calculs parallèles doivent être utilisés pour accélérer les opérations liées au processeur. Nous parlons ici d'opérations liées aux E / S. Votre implémentation doit être purement asynchrone , à moins que vous ne submergiez le cœur unique occupé sur votre processeur multicœur.
EDIT J'aime la suggestion faite par usr d'utiliser un "sémaphore asynchrone" ici.
la source
Utilisez
MaxDegreeOfParallelism
, qui est une option que vous pouvez spécifier dansParallel.ForEach()
:la source
GetStringAsync(url)
est destiné à être appelé avecawait
. Si vous inspectez le type devar html
, c'est unTask<string>
, pas le résultatstring
.Parallel.ForEach(...)
est destiné à exécuter des blocs de code synchrone en parallèle (par exemple sur différents threads).Essentiellement, vous allez vouloir créer une action ou une tâche pour chaque URL sur laquelle vous souhaitez accéder, les mettre dans une liste, puis traiter cette liste, en limitant le nombre qui peut être traité en parallèle.
Mon article de blog montre comment faire cela à la fois avec des tâches et avec des actions, et fournit un exemple de projet que vous pouvez télécharger et exécuter pour voir les deux en action.
Avec des actions
Si vous utilisez Actions, vous pouvez utiliser la fonction intégrée .Net Parallel.Invoke. Ici, nous le limitons à l'exécution d'au plus 20 threads en parallèle.
Avec des tâches
Avec les tâches, il n'y a pas de fonction intégrée. Cependant, vous pouvez utiliser celui que je propose sur mon blog.
Et puis en créant votre liste de tâches et en appelant la fonction pour les exécuter, avec un maximum de 20 tâches simultanées à la fois, vous pouvez le faire:
la source
ce n'est pas une bonne pratique car cela modifie une variable globale. ce n'est pas non plus une solution générale pour async. mais c'est facile pour toutes les instances de HttpClient, si c'est tout ce que vous recherchez. vous pouvez simplement essayer:
la source