Speicher freiräumen von Datatables und DGVs

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 18 Antworten in diesem Thema. Der letzte Beitrag () ist von EaranMaleasi.

    Speicher freiräumen von Datatables und DGVs

    Hallo,

    ich erzeuge mehrfach Datagridviews und da ich das mit Dispose, Speicherplatz und ähnlichem noch nicht ganz verstanden habe, denke ich ist das hier ein gutes Beispiel um einmal nachzufragen:

    Es läuft ungefähr folgendermaßen:
    Angelegt sind zwei Forms DgvFrm und Form1 und ein DataSet DS1 mit der Tabelle dtprint.

    Quellcode

    1. Public Class Form1
    2. Private dgvfrm As DgvForm
    3. Private Sub ptPrintView_Click(sender As Object, e As EventArgs) Handles btPrintView.Click
    4. dgvfrm = New DgvForm
    5. dgvfrm.Show()
    6. End Sub
    7. End Class
    8. Public Class DgvForm
    9. Private Sub DgvForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    10. dgvprintview.DataSource = Form1.DS1.dtprint
    11. dgvprintview.Columns(0).Width = 50
    12. dgvprintview.Columns(1).Width = 100
    13. dgvprintview.Columns(2).Width = 100
    14. dgvprintview.Columns(3).Width = 90
    15. dgvprintview.Columns(4).Width = 50
    16. dgvprintview.Columns(5).Width = 60
    17. dgvprintview.Columns(6).Width = 50
    18. dgvprintview.Columns(7).Width = 60
    19. dgvprintview.Columns(8).Width = 50
    20. dgvprintview.Columns(9).Visible = False
    21. End Sub
    22. End Class


    Das DgvFrm ist ganz simpel, nur ein DGV draufgezogen.

    Also ich rufe das zweite Form bei jedem ButtonClick auf. Das scheint schon irgendwie Probleme zu machen, da der Arbeitspeicher mit mehrere Knopfdrücken wächst. Aber ich weiß nicht warum

    Viele Grüße
    @Haudruferzappeltnoch Mit jedem Button_Click erstellst Du eine neue Instanz Deines Dialogs.
    Wenn Du möchtest, dass dur genau eine Instanz erstellt werden soll, dann mach es so:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private WithEvents frm2 As Form2
    2. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    3. If frm2 Is Nothing OrElse frm2.IsDisposed Then
    4. frm2 = New Form2
    5. End If
    6. If Not frm2.Visible Then
    7. frm2.Show(Me)
    8. End If
    9. If frm2.WindowState = FormWindowState.Minimized Then
    10. frm2.WindowState = FormWindowState.Normal
    11. End If
    12. End Sub
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich verstehe was du meinst und habe das ButtonClick-Event entsprechend erweitert.
    Jetzt kann ich nur noch ein DgvFrm gleichzeitig geöffnet haben, das ist sicherlich nicht schlecht.

    Dein Vorschlag hilft aber leider nicht was den Arbeitspeicher angeht, der wächst weiterhin, nach Schließen und neu Öffnen.
    @Haudruferzappeltnoch Bei diesem Codew ist nix zu sehen.
    Kannst Du ein kleinbes Beispielprojekt machen, das den Effekt reproduziert?
    ====
    Du greifst mit Form1.DS1.dtprint auf Daten zu.
    Ich nehme mal an, dass Form1 der Name der Form ist, und solit die VB6-Kompatibilitäts-Instanz.
    Wenn Du die Tabelle im Konstruktor übergibst, arbeitest Du nur auf einer Instanz:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private dgvfrm As DgvForm
    3. Private Sub ptPrintView_Click(sender As Object, e As EventArgs) Handles btPrintView.Click
    4. ' ...
    5. dgvfrm = New DgvForm(DS1.dtprint) ' dies hier
    6. ' ...
    7. dgvfrm.Show()
    8. End Sub
    9. End Class
    10. Public Class DgvForm
    11. Public Sub New(DataTable dtprint) ' ggf. Typ anpassen
    12. InitializeComponents()
    13. dgvprintview.DataSource = dtprint
    14. End Sub
    15. Private Sub DgvForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    16. dgvprintview.Columns(0).Width = 50
    17. dgvprintview.Columns(1).Width = 100
    18. dgvprintview.Columns(2).Width = 100
    19. dgvprintview.Columns(3).Width = 90
    20. dgvprintview.Columns(4).Width = 50
    21. dgvprintview.Columns(5).Width = 60
    22. dgvprintview.Columns(6).Width = 50
    23. dgvprintview.Columns(7).Width = 60
    24. dgvprintview.Columns(8).Width = 50
    25. dgvprintview.Columns(9).Visible = False
    26. End Sub
    27. End Class
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja Form1 heißt sowohl die Klasse als auch die Instanz, da hab ich selbst nichts instanziert, da es das Startform ist.
    Ich hab deinen Vorschlag erneut umgesetzt, da ich das deutlich besser finde. Jedoch kein Einfluss auf das Arbeitsspeicherverhalten

    Hier eine minimale Version, die dasselbe Verhalten zeigt.
    Dateien
    • ShowApp1.zip

      (107,76 kB, 40 mal heruntergeladen, zuletzt: )
    @Haudruferzappeltnoch Tut mir Leid, ich kann es zwar reproduzieren, aber abhelfen kann ich nicht.
    Ich hab es mit modalem Aufruf probiert, und wenn ich nach dem End Using ein GC.Collect(0) einfüge, steigt der Verbrauch nicht mehr an.
    Das kann allerdings nicht die Lösung sein!
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Haudruferzappeltnoch Ich sprach vom modalen Aufruf des Dialogs:

    VB.NET-Quellcode

    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2. Using dlg = New DgvForm(DS1.dtprint)
    3. dlg.ShowDialog(Me)
    4. End Using
    5. GC.Collect(0)
    6. End Sub
    Ja, GC ist der Garbage Collector, den zu bemühen sollte hier eigentlich nicht erforderlich sein!
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Spekulatius: Der GarbageCollector sieht einfach keine Notwendigkeit darin, diese kleinen, nicht mehr gebrauchten Objekte/Formleichen zu entsorgen. Bei 10 MB RAM-Belegung durch die App gähnt der GC wohl nur müde und denkt sich: ›Komm wieder und weck mich, wenn es um 1 GB geht.‹
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Der GC muss nunmal, wenn er etwas aufräumen möchte, deine Anwendung einmal komplett anhalten, sich alles ansehen, entscheiden was noch Kunst ist oder doch schon weg kann, und dann die Ausführung des Programmes fortsetzen. Daher versucht .NET den GC so selten wie nötig anzustoßen. Gerade in der jetzigen Zeit wo sogar die PCs normalsterblicher 8-16 GB RAM aufweisen ist, wenn gerade nichts weiteres läuft, 10 MB nicht würdig, um einen evtl. merklichen stotterer in deinem Programm zu erzeugen. Daher kann ein Programm schonmal etwas anwachsen bevor der GC aktiv wird.

    Gleichzeitig muss man aber auch aufpassen, dass diese kleinen Anstiege im RAM-Verbrauch nicht tatsächlich RAM-Lecks sind. Der GC kann eben auch nur dann aufräumen, wenn nichts mehr auf ein Objekt verweist. Hast du also noch irgendwelche Referenzen in deinem Code auf ein Objekt (oder Unterobjekt), so darf der GC das nicht aufräumen. Da hilft dann auch kein GC.Collect() mehr.
    Ich denke nicht. Da muss schon ein ziemlicher Brocken rumschwirren. Wenn da immer nur Krümel unentsorgt bleiben, wirst Du das wohl nicht großartig mitbekommen. Klar, wenn man einen Startwert der RAM-Belegung von 5 MB hat, im Laufe der Zeit auf 20 MB hochgeht und mit GC.Collect nur wieder auf 10 MB runterkommt, kann man vermuten, dass da noch irgendwas Unaufgeräumtes rumschwirrt. Aber da wäre es wohl besser, sich von der Codeanalyse unterstützen zu lassen, die darauf (falls richtig eingestellt) hinweist, wenn Objekte verwendet werden, die Dispose implementieren aber möglicherweise nicht disposed werden.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Haudruferzappeltnoch schrieb:

    gar nicht schlecht
    Doch.
    Ich verwende in einem einzigen Programm GC.Collect() zum Aufräumen des Speichers während eines Live-Bildes einer Kamera, und da werden größere Datenmengen bewegt.
    Bei Deinen "paar Bytes" sollte dies nicht erforderlich sein.
    Lies doch mal selbst in einer Schleife den Speicher aus, wenn sich der Dialog nach einer sehr kurzen Zeit (Timer) selbst beendet und gib die Werte im MainWindow aus.
    Vielleicht ist das ja auch eine Macke des Studios.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Ich verwende in einem einzigen Programm GC.Collect() zum Aufräumen des Speichers während eines Live-Bildes einer Kamera, und da werden größere Datenmengen bewegt.
    Und selbst da zweifel ich an, dass GC.Collect() was bringt.
    Hast du belastbare Tests, die aufzeigen, dass dein GC.Collect() tatsächlich zu einer Entlastung des Systems führt, im Vergleich zum Zustand, wenn man den GC seine Arbeit einfach machen lässt?

    Ansonsten habich das auch iwo aufgeschnappt, dass es Testing-Szenarient gibt, wo GC.Collect() sinnvoll ist.
    Aber eben für Tests - nicht für laufenden Betrieb.
    Welche Windows-Version nutzt du?

    Ab Vista gibt es eine sogenannte Prefatch-Funktion des Arbeitsspeichers. Das heißt, alles was Windows benötigt oder auch der Anwender häufiger startet, wird schon vorher in den RAM geschrieben, damit diese Programme schneller geladen werden können.
    Seit Windows 7 gibt es nun einen optimierten Arbeitsspeicher, der sich etwas anders verhält als die vorherigen Versionen. Vorher galt: Wird ein Programm nicht mehr benötigt, wird der Arbeitsspeicher entsprechend befreit.
    In der neuen Version läuft das Speicher nun einfach voll. Wenn ich Speicher allociere, dann wird er reserviert. Wenn ich den Speicher dann freigebe, wird der Speicher nun nicht mehr befreit, sondern er wird als reserviert gekennzeichnet. Dies hat den Vorteil, dass wenn man ihn nochmal brauchen sollte, er sofort wieder zur Verfügung steht. Es wird (ähnlich wie bei SSDs) erst der weitere freie Speicher verwendet. Erst wenn hier der gesamte Speicher zu klein wird, wird der reservierte Speicher wirklich befreit und zur Verfügung gestellt. Laut MS soll sich auch der GC mehr auf diese Momente konzentrieren. Man kann es sehr schön im TaskManager sehen, dass meist mehr Speicher in Verwendung ist als Prozesse tatsächlich benötigen. Im Ressourcenmanager sieht man dann auch, dass ein Großteil davon nicht wirklich verwendet, sondern reserviert ist.

    Ich kann mir also vorstellen, dass hier das Speichermanagement bei dir greift und erst weiterer freier Speicher für ein neues DGV verwendet wird und erst wenn dein Speicher vollgelaufen ist, anderer, nicht mehr referenzierter Speicher befreit und anschließend neu befüllt wird.

    ErfinderDesRades schrieb:

    Und selbst da zweifel ich an, dass GC.Collect() was bringt.
    Bei so was bei 30 FpS und 4000x3000 Mono-Pixeln, das war bei der Demo-Ansteuerungs-Software dabei, und ich habe mit den Leuten gesprochen.
    Mit Collect() lief es ruhiger.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Haudruferzappeltnoch schrieb:

    Das heißt zum Testen auf solche Lecks wäre GC.Collect gar nicht schlecht?
    Zum Testen auf RAM-Lecks gibt es seit mind. VS 2019 (kp. ob auch schon VS 2017 den hatte), einen Profiler, der dir haargenau aufzeigen kann, welche Objekte deines Programmes wie viel Speicher benötigen. Dazu legst du einen ersten Snapshot an, erledigst die Operation, von der du denkst, sie lässt keinen RAM frei, und machst dann einen weiteren Snapshot. Praktischerweise wird beim aufzeichnen eines Snapshots automatisch der GC angeworfen, sodass du auch wirklich nur die Objekte siehst, die tatsächlich noch "relevant" sind. Hast du also zwischen zwei Snapshots nen Anstieg an Objekten im RAM, kannst du nachsehen wo die herkommen.

    @ErfinderDesRades Ich hatte nun einmal das Problem, dass ich innerhalb einer do-while Schleife, wiederholt Daten von einem Server lade, und in eine Datenbank schreibe. Das können, je nach Tabelle bis zu mehreren Hundert MB sein. Nun handelte es sich hierbei um eine App und RAM ist in dieser Umgebung ein begrenztes Gut. Leider ist der GC innerhalb der Schleife nicht angesprungen, weswegen manche Geräte dann in eine OutOfMemory Exception gelaufen sind. Ein GC.Collect() hat dem Abhilfe geschaffen.