2016-07-06 15 views
2

Ich habe ein Stück Code, der über eine Sammlung Schleifen und ruft httpclient für jede Iteration. Die API, die der HTTP-Client aufruft, benötigt durchschnittlich 30 bis 40 ms zur Ausführung. Wenn ich es sequentiell anrufe, bekomme ich das erwartete Ergebnis, aber sobald ich Parallel.foreach verwende, dauert es länger. Wenn ich mich genau in den Logs umschaue, kann ich sehen, dass einige httpclient-Aufrufe mehr 1000ms benötigen, um ausgeführt zu werden, und dann fällt die Zeit wieder auf 30-40ms. Wenn ich in den API-Logs sehe, kann ich sehen, dass es kaum über 100ms geht. Ich bin mir nicht sicher, warum ich diesen Spike bekomme.parallel.foreach und httpclient - seltsames Verhalten

Der Code ist

using (var client = new HttpClient()) 
{ 
    var content = new StringContent(parameters, Encoding.UTF8, "application/json"); 
    var response = client.PostAsync(url, content); 
    _log.Info(string.Format("Took {0} ms to send post", watch.ElapsedMilliseconds)); 
    watch.Restart(); 

    var responseString = response.Result.Content.ReadAsStringAsync(); 
    _log.Info(string.Format("Took {0} ms to readstring after post", watch.ElapsedMilliseconds)); 
} 

Der Parallelruf ist so etwas wie dieses

Console.WriteLine("starting parallel..."); 
    Parallel.ForEach(recipientCollections, recipientCollection => 
     {  
     // A lot of processing happens here to create relevant content 
     var secondaryCountryRecipientList = string.Join(",",refinedCountryRecipients); 
     var emailApiParams = new SendEmailParametersModel(CountrySubscriberApplicationId, 
             queueItem.SitecoreId, queueItem.Version, queueItem.Language, countryFeedItem.Subject, 
             countryFeedItem.Html, countryFeedItem.From, _recipientsFormatter.Format(secondaryCountryRecipientList)); 

     log.Info(string.Format("Sending email request for {0}. Recipients {1}",          queueItem.SitecoreId, secondaryCountryRecipientList)); 

     var response = _notificationsApi.Invoke(emailApiParams); 
     }); 

dank

+0

Senden Sie alle diese Anforderungen an den gleichen Host? – spender

+0

Warum erstellen Sie 'HttpClient' für jeden Anruf? –

+1

Bitte posten Sie den gesamten Code. Da die Parallel-Anweisung fehlt, ist es schwer zu erkennen, wo das Problem liegt. Ich bemerke das Fehlen des Warte-Keywords auf PostAsync. – ManOVision

Antwort

2

Sie eine Überlastung der Server. Parallel hat keine Ahnung, wie viele Threads für Ihren spezifischen Web-Service optimal sind. Sie werden fehlerhafte Ergebnisse erhalten. In der Tat, wenn die Schleife für eine lange Zeit läuft, kann die Thread-Anzahl in die Hunderte und in die Tausende steigen (wirklich!). Ermitteln Sie empirisch das richtige DOP und fixieren Sie das DOP.

Wenn der Service überlastet ist, ist es nicht ungewöhnlich, sehr hohe Wartungszeiten zu sehen. Wie könnte es sonst sein? Es gibt nicht genug Kapazität, um es schnell zu tun.

var responseString = response.Result.Content.ReadAsStringAsync() 

Hier Sie vermissen einen .Result Anruf. Das Timing ist derzeit deaktiviert, aber dies ändert nichts an der Schlussfolgerung.

Möglicherweise treffen Sie auch das .NET-Limit für gleichzeitige Anforderungen für HTTP-Aufrufe. Der Standardwert ist 2.

+0

Eine weitere Möglichkeit besteht darin, dass der Code die 2 HTTP-Anforderungen pro Domänenlimit erreicht. –

+0

Natürlich, wie könnte ich das vergessen, da es schon 10k Fragen produziert hat. Bearbeitung. – usr

3

Standardmäßig lässt .NET nur 2 Verbindungen pro Server zu. Um dies zu ändern, müssen Sie den Wert von ServicePointManager.DefaultConnectionLimit zu einem größeren Wert ändern, zB 20 oder 100.

Dies verhindert nicht die Überflutung des Servers oder zu viel Speicher verbrauchen, wenn Sie zu viele Anfragen machen. Eine bessere Möglichkeit wäre, ein ActionBlock< T> zu verwenden Anfragen zu puffern und sie parallel in einer kontrollierten Funktion zu senden, zB:

ServicePointManager.DefaultConnectionLimit =20; 

var client = new HttpClient(); 

var blockOptions=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=10}; 

var emailBlock=new ActionBlock<SendEmailParametersModel>(async arameters=> 
    { 
     var watch=new Stopwatch(); 
     var content = new StringContent(parameters, Encoding.UTF8, "application/json"); 
     var response = await client.PostAsync(url, content); 
     _log.Info(..); 
     watch.Restart(); 

     var responseString = await response.Result.Content.ReadAsStringAsync(); 
     _log.Info(...); 
}); 

die E-Mails gesendet erfordert nicht parallel Aufruf mehr:

foreach(var recipientCollection in recipientCollections) 
{ 
    var secondaryCountryRecipientList = string.Join(",",refinedCountryRecipients); 
    var emailApiParams = new SendEmailParametersModel(CountrySubscriberApplicationId, queueItem.SitecoreId, queueItem.Version, queueItem.Language, countryFeedItem.Subject,countryFeedItem.Html, countryFeedItem.From, _recipientsFormatter.Format(secondaryCountryRecipientList)); 

    emailBlock.Post(emailApiParams); 
    log.Info(...); 
} 
emailBlock.Complete(); 
await emailBlock.Completion(); 

HttpClient ist Thread-sicher, mit dem Sie den gleichen Client für alle Anfragen verwenden können.

Der obige Code puffert alle Anfragen und führt sie 10 gleichzeitig aus. Der Aufruf von Complete() weist den Block an, alles abzuschließen und die Verarbeitung neuer Nachrichten zu beenden. await emailBlock.Completion() wartet auf alle vorhandenen Nachrichten, bevor Sie fortfahren

+0

Ich erhebe das, weil 'Parallel' eigentlich ein schreckliches Primitiv für IO ist. IO benötigt wirklich einen festen DOP und 'Parallel' hat nur ein Maximum. Meine eigene Lösung ist eine parallele asynchrone 'Select'-Hilfsmethode, aber das Framework hat keine. – usr

+1

@usr noch nicht - sie haben einen asynchronen Enumerator in CoreFxLab hinzugefügt, um Kanäle zu unterstützen. Kanäle wären eine großartige Alternative zum ActionBlock. –

+0

Wie ich es verstehe, ist ein Kanal für einzelne Hersteller Einzelverbraucher Fälle. Ich denke, wir brauchen nur eine parallele asynchrone Auswahl. Ich sehe Leute, die das ständig auf Stack Overflow brauchen, und dann hacken sie eine schlechte Lösung, so wie es hier passiert ist. – usr