2016-02-28 11 views
9

Ich habe TPL- und Aufgabenbibliotheksdokumente gelesen, um die Abdeckung abzudecken. Aber ich konnte den folgenden Fall immer noch nicht klar verstehen und gerade jetzt muss ich ihn umsetzen.Parallele Ausführung für E/A-gebundene Operationen

Ich werde meine Situation vereinfachen. Ich habe eine IEnumerable<Uri> der Länge 1000. Ich muss eine Anfrage für sie mit HttpClient.

Ich habe zwei Fragen.

  1. Es gibt nicht viel Rechen, wartet nur auf Http Anfrage. Kann ich in diesem Fall noch Parallel.Foreach() verwenden?
  2. Im Fall der Verwendung von Task, was ist die beste Praxis für die Erstellung einer großen Anzahl von ihnen? Nehmen wir an, ich verwende Task.Factory.StartNew() und füge diese Aufgaben zu einer Liste hinzu und warte auf alle. Gibt es eine Funktion (z. B. TPL-Partitionierer), die die Anzahl der maximalen Aufgaben steuert und maximal HttpClient kann ich erstellen?

Es gibt einige ähnliche Fragen zu SO, aber niemand erwähnt die Maximalen. Die Anforderung besteht lediglich darin, maximale Aufgaben mit maximalem HttpClient zu verwenden.

Vielen Dank im Voraus.

Antwort

11

In diesem Fall kann ich immer noch Parallel.Foreach verwenden?

Dies ist nicht wirklich angemessen. Parallel.Foreach ist mehr für CPU intensive Arbeit. Es unterstützt auch keine asynchronen Vorgänge.

Im Fall der Verwendung von Task, was ist die beste Praxis für die Erstellung einer großen Anzahl von ihnen?

Verwenden Sie stattdessen einen TPL Dataflow-Block. Sie erstellen keine großen Mengen von Aufgaben, die dort warten, bis ein Thread verfügbar wird. Sie können die maximale Anzahl an Aufgaben konfigurieren und sie für alle Elemente wiederverwenden, die sich in einem Puffer befinden und auf eine Aufgabe warten. Zum Beispiel:

var block = new ActionBlock<Uri>(
    uri => SendRequestAsync(uri), 
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 }); 

foreach (var uri in uris) 
{ 
    block.Post(uri); 
} 

block.Complete(); 
await block.Completion; 
+0

Was ist, wenn die Anzahl der gleichzeitigen Anfragen überschreitet die maximale Anzahl von HTTP-Anfrage Betriebssystem machen kann? – ozgur

+0

@ozgur Das hängt davon ab, wo dieses Limit konfiguriert ist. Aber wenn es einen gibt, dann stelle sicher, dass 'MaxDegreeOfParallelism' auf etwas niedriger als das gesetzt ist. – i3arnon

+0

Letzte Frage. Das von Ihnen bereitgestellte Beispiel ist für IO-Operationen geeignet, erfordert jedoch keine CPU-Parallelität? – ozgur

12

I3arnon die Antwort mit TPL Dataflow ist gut; Dataflow ist besonders nützlich, wenn Sie über eine Mischung aus CPU- und E/A-gebundenem Code verfügen. Ich stimme seinem Gefühl zu, dass Parallel für CPU-gebundenen Code entwickelt wurde; es ist nicht die beste Lösung für E/A-basierten Code und insbesondere nicht für asynchronen Code geeignet.

Wenn Sie eine alternative Lösung wollen, die gut mit meist-I/O-Code funktioniert - und benötigt keine externe Bibliothek - die Methode, die Sie suchen ist Task.WhenAll:

var tasks = uris.Select(uri => SendRequestAsync(uri)).ToArray(); 
await Task.WhenAll(tasks); 

Dies ist einfachste Lösung, aber es hat den Nachteil, alle Anfragen gleichzeitig zu starten. Insbesondere wenn alle Anfragen an denselben Dienst (oder eine kleine Gruppe von Diensten) gehen, kann dies zu Zeitüberschreitungen führen. Um dies zu lösen, müssen Sie eine Art Drosselung verwenden ...

Gibt es eine Funktion (z. B. TPL-Partitionierer), die die Anzahl der maximalen Aufgaben steuert und den maximalen HttpClient, den ich erstellen kann?

TPL Dataflow hat diese nette MaxDegreeOfParallelism, die nur so viele gleichzeitig startet. Sie können drosseln auch regelmäßige asynchronen Code durch einen anderen builtin verwenden, SemaphoreSlim:

private readonly SemaphoreSlim _sem = new SemaphoreSlim(50); 
private async Task SendRequestAsync(Uri uri) 
{ 
    await _sem.WaitAsync(); 
    try 
    { 
    ... 
    } 
    finally 
    { 
    _sem.Release(); 
    } 
} 

Bei Aufgabe anstelle, was ist die beste Praxis große Anzahl von ihnen für das Erstellen? Nehmen wir an, ich benutze Task.Factory.StartNew() und füge diese Aufgaben zu einer Liste hinzu und warte auf alle.

Sie wollen eigentlich nicht StartNew verwenden. Es gibt nur einen geeigneten Anwendungsfall (dynamic task-based parallelism), der extrem selten ist. Moderner Code sollte Task.Run verwenden, wenn Sie Arbeit auf einen Hintergrund-Thread schieben müssen. Aber das brauchst du nicht einmal, also ist weder StartNew noch Task.Run hier angebracht.

Es gibt einige ähnliche Fragen zu SO, aber niemand erwähnt die Höchstwerte. Die Anforderung besteht lediglich darin, maximale Aufgaben mit maximalem HttpClient zu verwenden.

Höchstwerte sind wo asynchronen Code wirklich knifflig wird. Mit CPU-gebundenem (parallelem) Code ist die Lösung offensichtlich: Sie verwenden so viele Threads, wie Sie Kerne haben. (Nun, zumindest können Sie starten dort und nach Bedarf anpassen). Bei asynchronem Code ist eine Lösung nicht so offensichtlich. Es hängt von vielen Faktoren ab - wie viel Speicher Sie haben, wie der Remote-Server reagiert (Geschwindigkeitsbegrenzung, Timeouts, etc.), etc.

Es gibt keine einfachen Lösungen hier. Sie müssen nur testen, wie Ihre spezifische Anwendung mit einem hohen Grad an Nebenläufigkeit umgeht, und dann auf eine niedrigere Zahl drosseln.


Ich habe einige slides for a talk, dass, wenn verschiedene Technologien geeignet sind, zu erklären versucht (Parallelität, Asynchronität, TPL Datenfluss, und Rx). Wenn Sie eher eine schriftliche Beschreibung mit Rezepten bevorzugen, können Sie von my book auf Nebenläufigkeit profitieren.

+2

Als Sie sagten, dass es keine einfache Lösung gibt, hat es meinen Schmerz beendet. Ich dachte, dass es wahrscheinlich einen Weg gibt, dies zu tun und und suchte Tag und Nacht. Jetzt kann ich versuchen, etwas spezifisch für meine eigene Situation zu implementieren. Vielen Dank. – ozgur