// 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);
});
Hier ist das Problem, es startet 1000+ gleichzeitige Webanfragen. Gibt es eine einfache Möglichkeit, die gleichzeitige Anzahl dieser asynchronen http-Anforderungen zu begrenzen? Damit werden nicht mehr als 20 Webseiten gleichzeitig heruntergeladen. Wie geht das am effizientesten?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Trauercodierer
quelle
quelle
HttpClient
istIDisposable
, und Sie sollten es entsorgen, besonders wenn Sie 1000+ von ihnen verwenden werden.HttpClient
kann als Singleton für mehrere Anforderungen verwendet werden.Antworten:
Sie können dies definitiv in den neuesten Versionen von Async für .NET mit .NET 4.5 Beta tun. Der vorherige Beitrag von 'usr' verweist auf einen guten Artikel von Stephen Toub, aber die weniger angekündigten Neuigkeiten sind, dass das asynchrone Semaphor es tatsächlich in die Beta-Version von .NET 4.5 geschafft hat
Wenn Sie sich unsere geliebte
SemaphoreSlim
Klasse ansehen (die Sie verwenden sollten, da sie leistungsfähiger als das Original istSemaphore
), bietet sie jetzt eineWaitAsync(...)
Reihe von Überladungen mit allen erwarteten Argumenten - Zeitüberschreitungsintervalle, Stornierungs-Token, all Ihre üblichen Planungsfreunde: )Stephen ist auch eine neuere Blog - Post über die neuen .NET 4.5 Goodies geschrieben , die mit Beta herauskam siehe Was ist neu für Parallelismus in .NET 4.5 Beta .
Zuletzt finden Sie hier einen Beispielcode zur Verwendung von SemaphoreSlim für die Drosselung asynchroner Methoden:
Zuletzt, aber wahrscheinlich eine Erwähnung wert, ist eine Lösung, die TPL-basierte Planung verwendet. Sie können delegatengebundene Aufgaben in der TPL erstellen, die noch nicht gestartet wurden, und einen benutzerdefinierten Aufgabenplaner zulassen, um die Parallelität zu begrenzen. Tatsächlich gibt es hier ein MSDN-Beispiel dafür:
Siehe auch TaskScheduler .
quelle
HttpClient
Parallel.ForEach
mit synchronem Code. Auf diese Weise können Sie asynchronen Code aufrufen.IDisposable
susing
odertry-finally
Aussagen einzuwickeln und ihre Entsorgung sicherzustellen.Wenn Sie über eine IEnumerable (dh URL-Zeichenfolgen) verfügen und mit jeder dieser Operationen gleichzeitig eine E / A-gebundene Operation ausführen möchten (dh eine asynchrone http-Anforderung erstellen) UND optional möchten Sie auch die maximale Anzahl gleichzeitiger Operationen festlegen E / A-Anforderungen in Echtzeit. So können Sie das tun. Auf diese Weise verwenden Sie Thread Thread et al. Nicht. Die Methode verwendet Semaphoreslim, um die maximale Anzahl gleichzeitiger E / A-Anforderungen zu steuern, ähnlich einem Schiebefenstermuster, das eine Anforderung abschließt, das Semaphor verlässt und die nächste eingeht.
Verwendung: Warten auf ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
quelle
using
wäre schön.Leider fehlen in .NET Framework die wichtigsten Kombinatoren für die Orchestrierung paralleler asynchroner Aufgaben. Es ist so etwas nicht eingebaut.
Schauen Sie sich die AsyncSemaphore- Klasse an, die von Stephen Toub erstellt wurde. Was Sie wollen, heißt Semaphor, und Sie benötigen eine asynchrone Version davon.
quelle
Es gibt viele Fallstricke und die direkte Verwendung eines Semaphors kann in Fehlerfällen schwierig sein. Daher würde ich empfehlen, das AsyncEnumerator NuGet-Paket zu verwenden, anstatt das Rad neu zu erfinden:
quelle
Das Beispiel von Theo Yaung ist nett, aber es gibt eine Variante ohne Liste wartender Aufgaben.
quelle
ProccessUrl
oder deren Unterfunktionen auftreten, werden tatsächlich ignoriert. Sie werden in Aufgaben erfasst, aber nicht an den ursprünglichen Anrufer von weitergeleitetCheck(...)
. Persönlich verwende ich deshalb immer noch Aufgaben und deren Kombinatorfunktionen wieWhenAll
undWhenAny
-, um eine bessere Fehlerausbreitung zu erzielen. :)SemaphoreSlim kann hier sehr hilfreich sein. Hier ist die Erweiterungsmethode, die ich erstellt habe.
Beispielnutzung:
quelle
Alte Frage, neue Antwort. @vitidev hatte einen Codeblock, der in einem von mir überprüften Projekt fast intakt wiederverwendet wurde. Nach einer Diskussion mit einigen Kollegen fragte man: "Warum verwenden Sie nicht einfach die integrierten TPL-Methoden?" ActionBlock sieht dort wie der Gewinner aus. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Wahrscheinlich wird sich kein vorhandener Code ändern, aber auf jeden Fall wird versucht, dieses Nuget zu übernehmen und Mr. Softys Best Practice für gedrosselte Parallelität wiederzuverwenden.
quelle
Hier ist eine Lösung, die die Faulheit von LINQ ausnutzt. Es entspricht funktional der akzeptierten Antwort , verwendet jedoch Worker-Tasks anstelle von a
SemaphoreSlim
, wodurch der Speicherbedarf des gesamten Vorgangs verringert wird. Lassen Sie es zunächst ohne Drosselung funktionieren. Der erste Schritt besteht darin, unsere URLs in eine Vielzahl von Aufgaben umzuwandeln.Der zweite Schritt besteht darin,
await
alle Aufgaben gleichzeitig mit der folgendenTask.WhenAll
Methode auszuführen :Ausgabe:
Durch die Implementierung von Microsoft wird
Task.WhenAll
die bereitgestellte Aufzählung für ein Array sofort materialisiert, sodass alle Aufgaben gleichzeitig gestartet werden. Das wollen wir nicht, weil wir die Anzahl der gleichzeitigen asynchronen Operationen begrenzen wollen. Wir müssen also eine Alternative implementierenWhenAll
, die unsere Aufzählung sanft und langsam auflistet. Wir werden dies tun, indem wir eine Anzahl von Worker-Tasks erstellen (die dem gewünschten Grad an Parallelität entsprechen), und jede Worker-Task zählt unsere aufzählbaren Aufgaben einzeln auf, wobei eine Sperre verwendet wird, um sicherzustellen, dass jede URL-Task verarbeitet wird von nur einer Arbeiteraufgabe. Dann müssen wirawait
alle Arbeiteraufgaben erledigen und schließlich die Ergebnisse zurückgeben. Hier ist die Implementierung:... und hier ist, was wir in unserem ursprünglichen Code ändern müssen, um die gewünschte Drosselung zu erreichen:
Es gibt einen Unterschied in der Behandlung der Ausnahmen. Der native
Task.WhenAll
wartet darauf, dass alle Aufgaben abgeschlossen sind, und aggregiert alle Ausnahmen. Die obige Implementierung wird sofort nach Abschluss der ersten fehlerhaften Aufgabe beendet.quelle
IAsyncEnumerable<T>
, finden Sie hier .Obwohl 1000 Aufgaben möglicherweise sehr schnell in die Warteschlange gestellt werden, kann die Bibliothek für parallele Aufgaben nur gleichzeitige Aufgaben verarbeiten, die der Anzahl der CPU-Kerne auf dem Computer entsprechen. Das bedeutet, dass bei einem Vier-Kern-Computer zu einem bestimmten Zeitpunkt nur vier Aufgaben ausgeführt werden (es sei denn, Sie verringern den MaxDegreeOfParallelism).
quelle
await
Schlüsselwort dort nicht gesehen. Das Entfernen sollte das Problem lösen, richtig?Running
Status) gleichzeitig ausführen als die Anzahl der Kerne. Dies ist insbesondere bei E / A-gebundenen Aufgaben der Fall.Parallele Berechnungen sollten verwendet werden, um CPU-gebundene Operationen zu beschleunigen. Hier geht es um E / A-gebundene Operationen. Ihre Implementierung sollte rein asynchron sein , es sei denn, Sie überwältigen den ausgelasteten Single Core auf Ihrer Multi-Core-CPU.
BEARBEITEN Ich mag den Vorschlag von usr, hier ein "asynchrones Semaphor" zu verwenden.
quelle
Verwenden
MaxDegreeOfParallelism
Sie diese Option, die Sie angeben können inParallel.ForEach()
:quelle
GetStringAsync(url)
soll mit gerufen werdenawait
. Wenn Sie den Typ von überprüfenvar html
, ist dies einTask<string>
, nicht das Ergebnisstring
.Parallel.ForEach(...)
ist zum parallelen Ausführen von synchronen Codeblöcken vorgesehen (z. B. auf verschiedenen Threads).Im Wesentlichen möchten Sie für jede URL, die Sie treffen möchten, eine Aktion oder Aufgabe erstellen, diese in eine Liste einfügen und diese Liste dann verarbeiten, um die Anzahl zu begrenzen, die parallel verarbeitet werden kann.
Mein Blog-Beitrag zeigt, wie dies sowohl mit Aufgaben als auch mit Aktionen gemacht wird, und enthält ein Beispielprojekt, das Sie herunterladen und ausführen können, um beide in Aktion zu sehen.
Mit Aktionen
Wenn Sie Aktionen verwenden, können Sie die integrierte .Net Parallel.Invoke-Funktion verwenden. Hier beschränken wir uns darauf, höchstens 20 Threads parallel auszuführen.
Mit Aufgaben
Bei Aufgaben ist keine Funktion integriert. Sie können jedoch die verwenden, die ich in meinem Blog zur Verfügung stelle.
Wenn Sie dann Ihre Aufgabenliste erstellen und die Funktion aufrufen, um sie ausführen zu lassen, beispielsweise maximal 20 gleichzeitig, können Sie Folgendes tun:
quelle
Dies ist keine gute Vorgehensweise, da eine globale Variable geändert wird. Es ist auch keine allgemeine Lösung für Async. Aber es ist einfach für alle Instanzen von HttpClient, wenn das alles ist, wonach Sie suchen. Sie können einfach versuchen:
quelle