Ich habe einen WCF-Dienst. Während der Arbeit des Dienstes muss er zwei Webdienste aufrufen. Es gibt also einen ähnlichen Code:
%Vor%Meistens funktioniert das OK, aber gelegentlich sah ich Spitzen in der Ausführungszeit, wo die erste Aufgabe ein paar Sekunden dauerte, um überhaupt zu beginnen. Als ich auf perfmon schaute, wurde mir klar, dass dies genau der Zeitpunkt war, als GC stattfand. Anscheinend war GC eine höhere Priorität, als meine Aufgaben zu erledigen. Dies ist nicht akzeptabel, da die Latenz für mich sehr wichtig ist und ich bevorzuge, dass GC zwischen Anfragen und nicht mitten in einer Anfrage stattfindet.
Ich habe versucht, einen anderen Weg zu gehen, und anstatt meine eigenen Aufgaben zu drehen, habe ich WebClient.DownloadStringTask .
%Vor%Das hat nicht geholfen; Der GC läuft jetzt, nachdem die Aufgabe begonnen hat, aber vor der Fortsetzung. Ich vermute mal, dass das System jetzt inaktiv ist, also ist es ein guter Zeitpunkt, GC zu starten. Nur kann ich mir die Latenz nicht leisten.
Die Verwendung von TaskCreationOptions.LongRunning, was dazu führt, dass der Scheduler Threads ohne Thread-Pool verwendet, scheint dies zu lösen, aber ich möchte nicht so viele neue Threads erstellen - dieser Code wird viel laufen (mehrmals pro Anfrage).
Was ist der beste Weg, um dieses Problem zu lösen?
Lassen Sie mich zuerst einige Missverständnisse beseitigen, die auf dieser Seite zu sehen sind:
Wie behebt man das?
Sie werden feststellen, dass es keine einfache Lösung gibt. Ich kenne keine, aber wenn das Problem von GC verursacht wird, wird das Problem durch eine der oben genannten behoben.
Ich weiß, dass Ihre Frage etwas mit GC zu tun hat, aber ich würde gerne zuerst über die asynchrone Implementierung sprechen und dann sehen, ob Sie immer noch die gleichen Probleme haben werden.
Wenn Sie den Beispielcode Ihrer ursprünglichen Implementierung nicht mehr benötigen, verschwenden Sie drei CPU-Threads, die gerade auf I / O warten:
Während der gesamten I / O zu Service1 und Service2 sind die drei CPU-Threads, die Sie verschwenden, nicht dazu geeignet, andere Arbeiten auszuführen, und der GC muss sie umgehen.
Meine erste Empfehlung wäre daher, Ihre WCF-Methode selbst so zu ändern, dass sie das von der WCF-Laufzeit unterstützte Muster des asynchronen Programmiermodells (APM) verwendet. Dadurch wird das Problem des ersten vergeudeten Threads gelöst, indem der ursprüngliche WCF-E / A-Thread, der in Ihre Dienstimplementierung aufgerufen wird, sofort in seinen Pool zurückgegeben wird, um andere eingehende Anforderungen bearbeiten zu können. Sobald Sie das getan haben, möchten Sie als nächstes die Aufrufe von Service1 und Service2 asynchron von der Client-Perspektive aus durchführen. Das würde eines von zwei Dingen beinhalten:
WebClient::DownloadStringAsync
implementation ( WebClient
ist nicht meine favorisierte API persönlich) HttpWebRequest::BeginGetResponse
+ HttpWebResponse::BeginGetResponseStream
+ HttpWebRequest::BeginRead
HttpClient
Wenn Sie all dies zusammenfügen, gäbe es keine überflüssigen Threads, während Sie auf eine Antwort von Service1 und Service2 in Ihrem Service warten. Der Code würde ungefähr so aussehen, wenn Sie eine WCF-Client-Route genommen haben:
%Vor%Sobald Sie alles an Ort und Stelle haben, werden Sie technisch gesehen KEINE CPU-Threads ausführen, während die I / O mit Service1 und Service2 aussteht. Dabei gibt es keine Threads für den GC, die sich sogar darum kümmern müssen, die meiste Zeit zu unterbrechen. Die einzige Zeit, in der die eigentliche CPU-Arbeit stattfindet, ist die ursprüngliche Planung der Arbeit und dann die Fortsetzung auf ContinueWhenAll, wo Sie alle Ausnahmen behandeln und die Ergebnisse massieren.
Ich empfehle Ihnen, Drews Antwort zu überdenken. Ein voll-asynchrones System wäre ideal.
Wenn Sie jedoch weniger Code ändern möchten, können Sie FromAsync
anstelle von StartNew
verwenden (dies erfordert asynchrone Proxys für Service1
und Service2
):
Dies reduziert die Anzahl der Threadpool-Threads pro WaitAll
von 3 auf 1. Sie sind immer noch nicht am Ideal (0), aber Sie sollten eine Verbesserung sehen.
Vielleicht möchten Sie dies versuchen, aber es könnte das Problem nur ein wenig auf den Kopf stellen:
%Vor%Wir empfehlen: Ссылка
Wenn Sie viele Webanforderungen ausführen, laden Sie eine Menge temporärer Objekte in den verwalteten Heap. Während der Heap wächst, wird der GC versuchen, etwas Speicher freizugeben, bevor ein neues GC-Segment zugewiesen wird. Dies ist der Hauptgrund, warum Sie sehen, GCs während Sie arbeiten.
Jetzt kommt der interessante Teil: Ihr GC Heap ist bereits 3 GB groß und Sie haben einige Web-Anfragen mit kurzlebigen Objekten zusätzlich auf dem GC-Heap. Volle GCs benötigen viel Zeit, um Ihr sicherlich komplexes Objektdiagramm (alle 3 GB) für tote Objekte zu durchlaufen. In einem solchen Hochdurchsatz-Szenario, in dem Sie für jede Anfrage eine erhebliche Menge an Temp-Daten über die Leitung erhalten, erzwingen Sie viele GCs.
An dieser Stelle sind Sie an die GC gebunden: Die Anwendungsleistung unterliegt nicht mehr Ihrer Kontrolle. Sie können dies normalerweise durch sorgfältiges Design Ihrer Datenstrukturen und Zugriffsmuster beheben, aber die GC-Zeiten werden größtenteils (ich denke & 95%) Ihre Anwendungsleistung dominieren.
Es gibt keinen einfachen Ausweg. Um die GC-Segmente kleiner zu machen, indem Sie Ihren Gesamtspeicherverbrauch überprüfen, kann es schwierig sein, wenn es ein großes komplexes System ist. Eine Alternative könnte darin bestehen, einen zusätzlichen Prozess (nicht eine neue Anwendungsdomäne, da der Anwendungsmanager überhaupt keine Anwendungsdomänen kennt) zu generieren und dort Ihre kurzlebigen Objekte in Ihren Webanforderungen zu erstellen. Dann können Sie aus dieser Unordnung herauskommen, wenn Sie in Ihrem kleinen Prozess eine sinnvolle Antwort berechnen können, die dann von Ihrem großen Serverprozess verwendet wird. Wenn Ihr Prozess eine gleiche Menge von temporären Daten erstellt, wie Ihre ursprünglichen Webanfragen, sind Sie wieder auf Platz eins und Sie haben nichts gewonnen.
Es kann hilfreich sein, Objekte aus früheren Webanforderungen wiederzuverwenden und einen Pool von Objekten bereit zu halten, um die Anzahl der Zuordnungen zu reduzieren.
Wenn Sie viele identische Strings in Ihrem Prozess-Heap haben, kann es hilfreich sein, sie zu puffern, wenn sie nie wieder freigegeben werden. Dies könnte helfen, Ihr Objektdiagramm zu vereinfachen.
Tags und Links .net task-parallel-library garbage-collection