2017-03-08 5 views
3

Ich habe (meine URL-Liste ist etwa 1000 URLs), ich frage mich, ob es eine effektivere Aufruf mehrere URLs von der gleichen Website (bereits die Änderung der ServicePointManager.DefaultConnectionLimit).Effektivere Methode, GetStringAsync mehrmals aufzurufen?

Auch ist es besser, die gleiche HttpClient wieder zu verwenden oder neue bei jedem Aufruf zu erstellen, unten verwendet nur eine anstelle von mehreren.

using (var client = new HttpClient { Timeout = new TimeSpan(0, 5, 0) }) 
{ 
    var tasks = urls.Select(async url => 
    { 
     await client.GetStringAsync(url).ContinueWith(response => 
     { 
      var resultHtml = response.Result; 
      //process the html 

     }); 
    }).ToList(); 

    Task.WaitAll(tasks.ToArray()); 
} 

wie @cory
hier ist der modifizierte Code mit TPL vorgeschlagen, aber ich die MaxDegreeOfParallelism = 100 gesetzt habe auf ca. gleiche Geschwindigkeit wie die Aufgabe zugrunde, zu erreichen, kann der folgende Code verbessert werden?

var downloader = new ActionBlock<string>(async url => 
{ 
    var client = new WebClient(); 
    var resultHtml = await client.DownloadStringTaskAsync(new Uri(url)); 


}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 }); 

foreach(var url in urls) 
{ 
    downloader.Post(url); 
} 
downloader.Complete(); 
downloader.Completion.Wait(); 

FINAL

public void DownloadUrlContents(List<string> urls) 
{ 
    var watch = Stopwatch.StartNew(); 

    var httpClient = new HttpClient(); 
    var downloader = new ActionBlock<string>(async url => 
    { 
     var data = await httpClient.GetStringAsync(url); 
    }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 }); 

    Parallel.ForEach(urls, (url) => 
    { 
     downloader.SendAsync(url); 
    }); 
    downloader.Complete(); 
    downloader.Completion.Wait(); 

    Console.WriteLine($"{MethodBase.GetCurrentMethod().Name} {watch.Elapsed}");  
} 
+0

Ich würde die Anzahl der mit TPL Datenfluss empfehlen zu begrenzen Aufgaben im Flug. Eine Sache, die Sie bei Ihrer aktuellen Implementierung finden werden, ist, dass die 'HttpClient'-Anfragen tatsächlich eine Zeitüberschreitung haben können, selbst wenn sie noch nicht im Netzwerk gesendet wurden. –

+0

So viel Parallellismus ist nutzlos, wenn Sie nicht viele Kerne auf der CPU haben, bekommen Sie nur den Thread-Hunger. Versuchen Sie '' erwarten SendAsync' anstelle von 'Post' zu verwenden, um einen Thread freizugeben, und blockieren Sie die Aufgaben nicht, verwenden Sie' warten' den ganzen Weg. HttpClient sollte von einem anderen Thread verwendet werden, also nicht jedes Mal ein neues erstellen – VMAtm

+0

@VMAtm Ich habe 6 Kerne auf meinem Rechner, ich bin ein bisschen verwirrt, können Sie mir zeigen, wie dieser Code aussehen würde? es scheint, ich gehe zu Aufgaben als meine erste Code-Nr? – Zoinky

Antwort

1

Obwohl Ihr Code funktioniert, ist es eine übliche Praxis, einen Pufferblock für Ihre ActionBlock einzuführen. Warum das tun? Der erste Grund ist die Größe der Aufgabenwarteschlange. Sie können die Anzahl der Nachrichten in Ihrer Warteschlange ganz einfach anpassen. Zweiter Grund ist, dass die Nachricht ist das Hinzufügen zu puffern fast sofort, und danach ist es TPL Dataflow Verantwortung all Ihre Artikel zu handhaben:

// async method here 
public async Task DownloadUrlContents(List<string> urls) 
{ 
    var watch = Stopwatch.StartNew(); 

    var httpClient = new HttpClient(); 

    // you may limit the buffer size here 
    var buffer = new BufferBlock<string>(); 
    var downloader = new ActionBlock<string>(async url => 
     { 
      var data = await httpClient.GetStringAsync(url); 
      // handle data here 
     }, 
     // note processot count usage here 
     new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); 
    // notify TPL Dataflow to send messages from buffer to loader 
    buffer.LinkTo(downloader, new DataflowLinkOptions {PropagateCompletion = true}); 

    foreach (var url in urls) 
    { 
     // do await here 
     await buffer.SendAsync(url); 
    } 
    // queue is done 
    buffer.Complete(); 

    // now it's safe to wait for completion of the downloader 
    await downloader.Completion; 

    Console.WriteLine($"{MethodBase.GetCurrentMethod().Name} {watch.Elapsed}"); 
} 
+0

Ihre Verwendung von 'BufferBlock' führt hier nichts aus. Was war die Absicht? –

+0

@CoryNelson Wie gesagt, es ist bequemer, die Eigenschaft 'ActionBlock' 'BoundedCapacilety' zu verwenden, anstatt sie zu begrenzen. Hier teilen Sie die Logik für * Speichern * der Nachrichten und * Behandeln * sie. Das ist besser, als ich denke. – VMAtm

+0

Der 'ActionBlock' hat bereits einen eingebauten Puffer - Sie verbessern die Dinge nicht durch Hinzufügen eines anderen Puffers davor. 'BufferBlock' hat sehr begrenzte Anwendungsfälle, wirklich nur um nichtlineare Datenflüsse, die verzweigen. –

0

Im Wesentlichen wieder mit den HttpClient ist besser, weil Sie nicht haben Sie authentifizieren sich jedes Mal, wenn Sie eine Anfrage senden, und Sie können den Status einer Sitzung mithilfe von Cookies speichern, es sei denn, Sie initialisieren sie mit einem Token/Cookies bei jeder Erstellung. Ansonsten kommt alles auf ServicePoint, wo Sie die maximal zulässige Anzahl gleichzeitiger Verbindungen einstellen können.

Um in mehr wartbar Weise Anrufe parallel zu tun, würde ich vorschlagen, die AsyncEnumerator NuGet package, verwenden, die Sie einen Code wie diesen schreiben können:

using System.Collections.Async; 

await uris.ParallelForEachAsync(
    async uri => 
    { 
     var html = await httpClient.GetStringAsync(uri, cancellationToken); 
     // process HTML 
    }, 
    maxDegreeOfParallelism: 5, 
    breakLoopOnException: false, 
    cancellationToken: cancellationToken); 
Verwandte Themen