Warum ist der Speicherverbrauch kritisch zu betrachten?
Bei der Anwendungsentwicklung wird der Speicherverbrauch (RAM) dann kritisch, wenn eine Anwendung als zentraler Bestandteil oder von einer steigenden Anzahl von Benutzern eingesetzt wird.
Hierbei wird die Situation im Kontext von .Net und C# betrachtet.
Wie ist das zu verstehen, was genau meint das?
Ein Serversystem läßt sich heutzutage natürlich leicht skalieren, da gibt's doch Performance ohne Ende, oder?
Ja und nein, denn hier steht der Arbeitsspeicher und der Bedarf einzelner Prozesse im Fokus. Und dann sind doch da bestimmt noch andere Programme/Prozesse, die auf dem Serversystem performant laufen sollen, stimmt's?!
Eine höhere CPU-Power hat insofern Einfluß, als das der Speicherbedarf eines langlebigen Prozesses nur schneller an die Grenzen gelangt oder Rechen-Power für das Swappen abgezweigt wird (Auslagern vom RAM auf die SSD und zurück).
Die Grenze liegt aktuell bei bis zu 4GB, dann gibt's eine OutOfMemoryException
.
Bei Cloud-Lösungen kommen noch die Verrechnung von Datenverbrauch hinzu, mit unterschiedlichen Metriken.
Deswegen ist es wichtig, daß nicht nur IDisposable
implementiert wird, sondern auch aufgerufen werden darf; am besten automatisch.
Zusätzliche Log-Ausgaben zur Laufzeit zu sammeln hilft, um die entsprechenden Stellen zu orten und erste Abhilfe zu schaffen. Das ist an anderer Stelle bereits beschrieben worden.
Die Verwendung eines DI-Containers stellt dabei eine flexible Lösung dar. Dazu setze ich seit Jahren Autofac.org ein.
Neben der üblichen Registrierung von Services, Model-Typen und DTOs, natürlich auch mit Attribut via Reflection, gibt es auch seltene Situationen, wo nicht direkt der DI-Container für die Objekterzeugung aufgerufen werden kann.
Für diesen Fall gibt es die Funktionalität von Disposer.AddInstanceForDisposal(IDisposable)
, um die Aufräumarbeiten später automatisch erledigen zu lassen.
Hier ein Beispiel, wobei nicht zwingend ein neuer Lifetime-Scope erstellt werden muß, sondern oft ein Scope vorhanden ist (Session-/Request-basiert). Einen neuen Lifetime-Scope zu erstellen wäre zu überlegen, wenn eine Übertragung von Massendaten stattfindet, dann wäre ein Scope für jeden Datenblock sinnvoll.
public sealed class MyListClass : List<MaterialDto>, IDisposable
{
public void Dispose()
{
// Dispose-Logik auf die Listenelemente anwenden
// TODO to reduce workload of the GC
if (disposing)
GC.SuppressFinalize(this);
}
}
// Verwendung im Code
using (var scope = container.BeginLifetimeScope())
{
var myObject = new MyListClass();
scope.Disposer.AddInstanceForDisposal(myObject);
// hier die Dto-Objekte hinzufügen, welche später automatisch auch disposed werden sollen
// ...
// Beim Verlassen des 'using' Blocks wird 'myObject.Dispose()' aufgerufen
}
Weiterführende Links
- https://docs.autofac.org/en/latest/register/parameters.html
- https://groups.google.com/g/autofac/c/gclizZzdvhc?pli=1
- https://github.com/alexandrnikitin/Topshelf.Autofac
- https://stackoverflow.com/questions/9066200/passing-parameters-to-constructors-using-autofac
Performance und der Einsatz von Referenzen