GDI+ - Arbeitsspeicher läuft voll

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

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von J.Herbrich.

    GDI+ - Arbeitsspeicher läuft voll

    Hallo,

    ich habe bei meiner GDI+ Routine das Problem, dass der Arbeitsspeicher extrem schnell volläuft, etwa 2-3 MB pro Frame den ich rendere. Bei ca. 600 - 800 Frames ist dann Schluss - OutOfMemory-Exception

    Ich habe hier mal den Code gepostet... eigentlich dispose ich alles was ich aufmache, aber irgendwas muss hier noch versteckt sein.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub drawFrame()
    2. If Not IsNothing(picBoxGauge.BackgroundImage) Then
    3. picBoxGauge.BackgroundImage.Dispose()
    4. End If
    5. Dim pic As New Bitmap(350, 350)
    6. Dim g As Graphics = Graphics.FromImage(pic)
    7. Dim backgroundBrush As New SolidBrush(Color.Black)
    8. Dim speedFont As New Font("Arial", 20, FontStyle.Bold)
    9. Dim speedXpos As Integer
    10. Dim f1t_overlay As Image = My.Resources.f1t_overlay
    11. Dim f1t_bg As Image = My.Resources.f1t_bg
    12. Dim f1t_drs_active As Image = My.Resources.f1t_drs_active
    13. Select Case frames(frameCounter).speed.ToString.Length
    14. Case 1
    15. speedXpos = 133
    16. Case 2
    17. speedXpos = 124
    18. Case 3
    19. speedXpos = 115
    20. End Select
    21. With g
    22. 'Draw the background
    23. .DrawImage(f1t_bg, 0, 0, 350, 350)
    24. 'Draw throttle, brake, speed, gear, rpm and kers
    25. .FillRectangle(Brushes.Green, 92, 233, 105 * frames(frameCounter).throttle + 1, 9)
    26. .FillRectangle(Brushes.Red, 92, 245, 105 * frames(frameCounter).brake + 1, 9)
    27. .DrawString(frames(frameCounter).speed, speedFont, Brushes.White, speedXpos, 122)
    28. 'Draw drs and overlay
    29. .DrawImage(My.Resources.f1t_overlay, 0, 0, 350, 350)
    30. End With
    31. picBoxGauge.BackgroundImage = Image.FromHbitmap(pic.GetHbitmap)
    32. g.Dispose()
    33. pic.Dispose()
    34. backgroundBrush.Dispose()
    35. speedFont.Dispose()
    36. f1t_bg.Dispose()
    37. f1t_drs_active.Dispose()
    38. f1t_overlay.Dispose()
    39. End Sub



    EDIT: Ich habe gerade auch nochmal im Taskmanager nachgeschaut, ich kriege pro Frame ein GDI-Objekt dazu -> sehr wahrscheinlich das Bild, das diese 2-3MB braucht und damit den Arbeitsspeicher vollspammt
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „newcat“ ()

    Und versuch doch mal direkt auf die PictureBox zu zeichnen und nicht das Hintergrundbild zu setzen

    VB.NET-Quellcode

    1. Private Sub picBoxGauge_Paint(sender As Object, e As PaintEventArgs) Handles picBoxGauge.Paint
    2. e.Graphics.DrawImage(f1t_bg, 0, 0, 350, 350)
    3. End Sub

    Hab jetzt mal den Code etwas geändert, die Variablen waren eh nur zu Testzwecken da unten, allerdings tritt das Problem immernoch auf.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim pic As New Bitmap(350, 350)
    2. Dim g As Graphics = Graphics.FromImage(pic)
    3. Dim backgroundBrush As New SolidBrush(Color.Black)
    4. Dim speedFont As New Font("Arial", 20, FontStyle.Bold)
    5. Dim speedXpos As Integer
    6. Dim f1t_overlay As Image = My.Resources.f1t_overlay
    7. Dim f1t_bg As Image = My.Resources.f1t_bg
    8. Dim f1t_drs_active As Image = My.Resources.f1t_drs_active
    9. Private Sub drawFrame()
    10. If Not IsNothing(picBoxGauge.BackgroundImage) Then
    11. picBoxGauge.BackgroundImage.Dispose()
    12. End If
    13. Select Case frames(frameCounter).speed.ToString.Length
    14. Case 1
    15. speedXpos = 133
    16. Case 2
    17. speedXpos = 124
    18. Case 3
    19. speedXpos = 115
    20. End Select
    21. With g
    22. .Clear(Color.Transparent)
    23. 'Draw the background
    24. .DrawImage(f1t_bg, 0, 0, 350, 350)
    25. 'Draw throttle, brake, speed, gear, rpm and kers
    26. .FillRectangle(Brushes.Green, 92, 233, 105 * frames(frameCounter).throttle + 1, 9)
    27. .FillRectangle(Brushes.Red, 92, 245, 105 * frames(frameCounter).brake + 1, 9)
    28. .DrawString(frames(frameCounter).speed, speedFont, Brushes.White, speedXpos, 122)
    29. 'Draw drs and overlay
    30. .DrawImage(My.Resources.f1t_overlay, 0, 0, 350, 350)
    31. End With
    32. picBoxGauge.BackgroundImage = Image.FromHbitmap(pic.GetHbitmap)
    33. End Sub



    @Bluespide
    Ja werde ich mal probieren... das Hauptziel ist eigentlich auch gar nicht das Anzeigen der Bitmaps sondern eigentlich das Speichern, man soll sozusagen nur den Fortschritt auch sehen können, deshalb dachte ich diese Lösung wäre eleganter
    EDIT: Hmm vielleicht bin ich zu blöd dafür, ich bekomme nur ein rotes Kreuz angezeigt... Musste man da nicht irgendwie erst einstellen, dass man selber zeichnen möchte? Wenn ja bin ich definitiv zu blöd den entsprechenden Parameter zu finden.
    EDIT2: Okay, Fehler gefunden: Ich hatte noch ne NullPointerException drin - die wurde mir natürlich nicht angezeigt. Jetzt funktioniert es auch - aber was hätte ich disposen müssen, damit es mit den Bitmaps funktioniert hätte?
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „newcat“ ()

    Sorry für den Doppelpost, aber eigentlich ist das hier ein neues (altes Problem):

    Wenn ich jetzt so die Bitmaps speichern möchte, bekomme ich wieder das RAM-Problem:

    VB.NET-Quellcode

    1. picBoxGauge.Update()
    2. picBoxGauge.DrawToBitmap(pic, New Rectangle(0, 0, 350, 350))
    3. pic.Save(filePath & "\f1t_" & frameCounter.ToString & ".png", System.Drawing.Imaging.ImageFormat.Png)


    Ich vermute, dass irgendwo im System jede "Version", also jeder Frame, des pic gespeichert wird, auch wenn ich es im nächsten Durchlauf überschreibe. Dass dadurch der benutzte Speicher in die Höhe schießt ist klar, aber ich muss doch irgendwie diese "versteckten" GDI-Objekte löschen können oder?
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer
    Hallo, Mal ne gant kleine Frage?

    Ich selber habtte früher ein "schrott pc" als zweit rechner mit 256MB Ram, (st nicht peinlich jungs^^) und da hatte ch halt auch bei Code der scheinbar normal lief auf meinen Haupt-PC überall OutOfMemory Exceptions. Ich habe jetzt mal alten code hervorgecramt der reines GDI nutzt und da hackt auch das ganze Programm, zwar keine Exception aber der Tastmanager spricht für sich.

    LG, Herbrich
    Das mit dem Picturebox.backgroundImage.Dispose() hab ich im oberen Code schonmal probiert, das hat da allerdings auch nicht funktioniert; ich werds aber auch nochmal im neuen Code probieren.

    @ErfinderDesRades
    Ich speicher jeden Frame weil ich dann später mit Blender die ImageSequence als Overlay über das eigentliche Video lege. Dazu brauche ich Bilder mit Alpha-Channel. Die Bilder sind allerdings auch nicht groß, pro PNG nur 25kb, da komme ich selbst bei langen Recordings nicht in den GB Bereich.

    EDIT: Hatte vergessen den neuen Code fürs Paint-Event zu posten, hier ist er:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub picBoxGauge_Paint(sender As Object, e As PaintEventArgs) Handles picBoxGauge.Paint
    2. If Not IsNothing(frames) Then
    3. Select Case frames(frameCounter).speed.ToString.Length
    4. Case 1
    5. speedXpos = 132
    6. Case 2
    7. speedXpos = 121
    8. Case 3
    9. speedXpos = 110
    10. End Select
    11. Select Case frames(frameCounter).gear
    12. Case 0
    13. gearString = "N"
    14. Case 10
    15. gearString = "R"
    16. Case Else
    17. gearString = frames(frameCounter).gear
    18. End Select
    19. With e.Graphics
    20. .Clear(Color.FromKnownColor(KnownColor.Control))
    21. 'Draw the background
    22. .DrawImage(f1t_bg, 0, 0, 350, 350)
    23. 'Draw throttle, brake, speed, gear, rpm and kers
    24. .FillRectangle(Brushes.Green, 92, 233, 105 * frames(frameCounter).throttle + 1, 9)
    25. .FillRectangle(Brushes.Red, 92, 245, 105 * frames(frameCounter).brake + 1, 9)
    26. .DrawString(frames(frameCounter).speed, speedFont, Brushes.White, speedXpos, 120)
    27. .DrawString(gearString, gearFont, Brushes.White, 135, 180)
    28. 'TODO: RPM algorithms
    29. .FillRectangle(Brushes.Red, 275, CInt(212 - 76 * (frames(frameCounter).kers / 400000)), 21, CInt(76 * (frames(frameCounter).kers) / 400000) + 1)
    30. 'Draw drs and overlay
    31. If frames(frameCounter).drs = 1 Then
    32. .DrawImage(My.Resources.f1t_drs_active, 0, 0, 350, 350)
    33. End If
    34. .DrawImage(My.Resources.f1t_overlay, 0, 0, 350, 350)
    35. End With
    36. End If
    37. End Sub



    EDIT2: Habe gerade gemerkt, dass nachdem alle Frames gerendert sind, der Arbeitsspeicherverbrauch wieder drastisch sinkt. Hat vielleicht die GC nicht genug Zeit alles zu löschen? Ich habe es auch schonmal mit GC.Collect(100, GCCollectionMode.Forced, True) versucht, allerdings hat das keinen Unterschied gemacht.
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „newcat“ ()

    newcat schrieb:

    VB.NET-Quellcode

    1. Select Case frames(frameCounter).speed.ToString.Length
    Hier gibt es kein Case Else. Ist das korrekt?
    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:

    newcat schrieb:

    VB.NET-Quellcode

    1. Select Case frames(frameCounter).speed.ToString.Length
    Hier gibt es kein Case Else. Ist das korrekt?


    Korrekt. Speed geht von 0 bis etwa 350

    EDIT:
    Nachdem mir aufgefallen ist, dass das Abspielen ja funktioniert, obwohl es auf dem gleichen Prinzip basiert, habe ich jetzt einen Timer mit dem Intervall 1ms eingefügt und mit diesem die While-Schleife ersetzt. Scheint so, als ob die GC jetzt genug Zeit hat um die ganzen alten Bilder zu löschen, zumindest bleibt der benötigte Arbeitsspeicher jetzt unter 200MB. Das ist zwar definitiv nicht die eleganteste Lösung, besonders im Hinblick auf Performance, aber immerhin tut es so!
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „newcat“ ()

    Was spricht eigentlich gegen einen TryCahtch block (falls nicht schon vorhanden) mit eingebauten Finally, alle Variablen werden dann einfach vor den TryCatch Block definiert und anschlißend in Finally gecleart (also auf Nothing) gesetzt. Dann sollte es doch eigentlich ja funktionieren oder nicht?

    LG, Herbrich
    Ich hab ein ähnliches Problem, allerdings bin ich noch nie in einen Speicherüberlauf gelaufen. Die Frage die ich mir stelle ist, muss man nun am Ausgang einer Funktion die selbst erstellten Objekte (Bitmaps, Grafik-Obj., Pens und Brushes) disposen oder macht VB das selbst?

    Klaus
    Man sollte.
    Die IDisposable-Schnittstelle und das Using-Schlüsselwort sind nunmal dafür geschaffen, dass nicht mehr gebrauchte Objekte damit aufgeräumt werden.
    Viele Klassen implementieren IDisposable.
    Bei manchen wirkt es sich gravierend aus, wenn das Disposen vergessen wird, bei anderen ist zT. gar kein Effekt merkbar.

    Aber imo bist du auf der sicheren Seite, wenn du disposest, was du nicht mehr brauchst.
    Jep, ich habe selber mal zum Dispose Pattern hier ein par Tips in Forum geschrieben :)
    Scheint ja nicht zu reichen, das Disposen. Wenn die GC nicht zum Zug kommt (auch wenn ich sie force, warum auch immer) wird der Speicher trotzdem nicht freigegeben. Ich nehme an, es läuft hier irgendetwas im Framework, was ständig die Bilder speichert, aber nicht automatisch Disposed, und die GC kommt in einer while-Schleife einfach nicht dran; ich frage mich aber, warum GC.Collect(100, GCCollectionMode.Forced, True) (ob ich als Generation 1 oder 40000 nehme, ist egal) dann nicht funktioniert - theoretisch müsste doch dann so ein synchroner Aufruf der GC erfolgen. Wiegesagt, die Timer-Lösung funktioniert, deshalb habe ich den Thread auch mal als gelöst markiert; falls aber jemand noch eine Idee hat, wie man das ganze effizienter lösen könnte, darf sie hier gerne loswerden - ich würde auch Code und benötigte Dateien hochladen, falls das jemand braucht.
    Twitch Viewer Display Chat-, Zuschauer- und Statistiktool für Streamer
    ich nehme einfach an, du disposest da iwas nicht.

    Und den Königsweg nannte ich bereits in post#2: Erstell einfach nicht so massenhaft von dem Zeugs, sondern wiederverwende esauch mal hin und wieder.
    Aber kannst auch gerne ein Testprojekt zippen und anhängen - nur sollte es keinen Bin-Ordner enthalten.

    Und probier vorher selber aus, obs entpackt und läuft.
    Disposen reict immer aus, ich inplementiere ja auch in jede klasse von mir die IDisposiable Schnittstelle. Weil Dispose einfach zu einfach ist. Man setzt in grundgenommen alles wieder auf Nothing und das wars.

    Das wird dann von GC Aufgerufen und ausgeführt.

    LG, Herbrich
    Hallo,

    Ja, dass ist mir durchaus schon klar, jedoh hindert ein nichts (wie ja schon gesagt) Dispose zu einen Zeitpunkt aufzurufen der mir passt.

    LG, J. Herbrich