Windows Form App -- 2te Form um den belegten Speicher freizugeben

  • VB.NET
  • .NET (FX) 4.0

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Windows Form App -- 2te Form um den belegten Speicher freizugeben

    Hi,

    ich bin neu hier und weiß nicht so recht wie ich anfangen soll.
    Ich habe ein grundsätzliches Problem mit einer Bibliothek, diese habe ich von Extern bekommen. Leider gibt es in dieser Bibliothek einen Memory Leak. Der sich nach 8Tagen/24h der Benutzung bemerkbar macht.
    Diese Bibliothek ermöglicht das Empfangen von Bildern, von einer speziellen Kamera. Ich muss das Programm also öfters neu starten.
    Jetzt habe ich mir gedacht ich lass die Aufgaben in einer 2ten Form laufen, um sie dann zyklisch zu schließen damit ich sie wieder starten kann.
    Ich bin davon ausgegangen das beim Schließen der 2ten Form der Speicher wieder freigegeben wird. Dies ist leider nicht so.

    Ich habe mir ein Bibliothek/Klasse erstellt die mir einen Speicher Leak erzeugt.
    Dann habe ich ein Projekt erstellt mit 2 Formen. Die "Start" Form enthält 2 Button. Die "Bilder" Form enthält den Verweis zur Bibliothek/Klasse.
    Durch den "Start" Button wird die Form "Bilder" geöffnet und der Speicher wird gefüllt. Mit dem "Stop" Button wird die erstellte Form wieder geschlossen.
    Durch das Schließen der Form wird wie gesagt leider nicht der Speicher freigegeben.


    Nun meine Fragen, ist es überhaupt über diesen Weg möglich? bzw. habt ihr eine Idee wie ich dieses Problem lösen könnte?
    Kann man die "Bilder" Form so aufrufen, das sie wie ein eigenes Programm behandelt wird?

    Ich danke schon mal im Voraus für eure Bemühungen.


    Bibliothek/Klasse die mir den Speicher Leak erzeugt:

    VB.NET-Quellcode

    1. Imports System.Drawing
    2. Imports System.Windows.Forms
    3. Public Class Bilder_Klasse
    4. Public listebild As New List(Of Bitmap)
    5. Public Text1 As String = ""
    6. Public Text2 As String = ""
    7. Public Text3 As String = ""
    8. Public Text4 As String = ""
    9. Public Text5 As String = ""
    10. Public Text6 As String = ""
    11. Public Text7 As String = ""
    12. Public Text8 As String = ""
    13. Public Pfad_Image As String = ""
    14. WithEvents Timer_Start As New Timer
    15. Public Sub Start_Klasse()
    16. Timer_Start.Start()
    17. End Sub
    18. Public Sub Stop_Klasse()
    19. Timer_Start.Stop()
    20. End Sub
    21. Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer_Start.Tick
    22. Text1 = Process.GetCurrentProcess().StartTime
    23. Text2 = Process.GetCurrentProcess().Id
    24. Text3 = listebild.Count
    25. Text4 = (Process.GetCurrentProcess().PagedMemorySize64 / (1024 ^ 2)).ToString
    26. Text5 = (Process.GetCurrentProcess().PagedSystemMemorySize64 / (1024 ^ 2)).ToString
    27. Text6 = My.Computer.Info.TotalVirtualMemory / (1024 ^ 3)
    28. Text7 = (Process.GetCurrentProcess().PrivateMemorySize64 / (1024 ^ 2)).ToString
    29. Text8 = (Process.GetCurrentProcess().WorkingSet64 / (1024 ^ 2)).ToString
    30. Dim test As Bitmap = New Bitmap(Pfad_Image)
    31. listebild.Add(test)
    32. End Sub
    33. End Class




    Start Form:

    VB.NET-Quellcode

    1. Public Class Start
    2. Shared t As New Threading.Thread(AddressOf Start_Form)
    3. Private Shared Sub Start_Form()
    4. Application.Run(New Bilder)
    5. End Sub
    6. Shared Sub ShowForm_Async()
    7. t.Start()
    8. End Sub
    9. Shared Sub CloseForm_Async()
    10. t.Abort()
    11. End Sub
    12. Private Sub bt_start_Click(sender As Object, e As EventArgs) Handles bt_start.Click
    13. bt_start.Enabled = False
    14. t = New Threading.Thread(AddressOf Start_Form)
    15. ShowForm_Async()
    16. bt_stop.Enabled = True
    17. End Sub
    18. Private Sub bt_stop_Click(sender As Object, e As EventArgs) Handles bt_stop.Click
    19. bt_stop.Enabled = False
    20. CloseForm_Async()
    21. bt_start.Enabled = True
    22. End Sub
    23. End Class



    Bilder Form:

    VB.NET-Quellcode

    1. Public Class Bilder
    2. Dim Test_Klasse As ClassLibrary1.Bilder_Klasse
    3. Private Sub t_refresh_Tick(sender As Object, e As EventArgs) Handles t_refresh.Tick
    4. lbl1.Text = Test_Klasse.Text1
    5. lbl2.Text = Test_Klasse.Text2
    6. lbl3.Text = Test_Klasse.Text3
    7. lbl4.Text = Test_Klasse.Text4
    8. lbl5.Text = Test_Klasse.Text5
    9. lbl6.Text = Test_Klasse.Text6
    10. lbl7.Text = Test_Klasse.Text7
    11. lbl8.Text = Test_Klasse.Text8
    12. End Sub
    13. Private Sub Bilder_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    14. Test_Klasse = New ClassLibrary1.Bilder_Klasse
    15. Test_Klasse.Pfad_Image = "C:\Users\Bernd\Pictures\maik.bmp"
    16. Test_Klasse.Start_Klasse()
    17. t_refresh.Start()
    18. End Sub
    19. Private Sub Bilder_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
    20. Test_Klasse.Stop_Klasse()
    21. t_refresh.Stop()
    22. End Sub
    23. End Class



    Freundliche Grüße
    sITrAiN
    Schreib doch nen separates Programm welches den Start und das Ende des Kameratool steuert. Sowas wie nen "Launcher".
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    @sITrAiN Willkommen im Forum. :thumbup:
    [OT]Du nix deutsch?
    Deutsch: 2. Form
    Englisch: 2nd form
    Weder - noch. ;( [/OT]
    ======
    Es genügt der Timer in der Class Bilder, dort holst Du Dir die Informationen und zeigst sie an.
    Das Problem bei Bildern ist stets, dass sie unmanaged Memory belegen.
    Deswegen müssen Bilder, die fertig bearbeitet wurden oder die überschrieben werden, disposed werden :!:
    Ich sehe nur, dass Du Bilder an eine List(Of Bitmap) hängst.
    Klar, dass diese Liste Dir den Speicher zumüllt.
    Bevor Du die Liste löschst, musst Du die Bilder zunächst disposen:

    VB.NET-Quellcode

    1. For Each bmp in

    VB.NET-Quellcode

    1. listebild
    2. bmp.Dispose()
    3. Next
    4. listebild.Clear()
    Teste mal dies.
    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!
    Hi,

    eher Deutsch ;) nur so lange wie ich es halt nicht besser weiß, dann kann schon mal was komisches bei rauskommen. Entschuldigung.

    Du hast Recht, aber das möchte ich ja nicht. Ich habe diese Class Bilder gebaut um die erwähnte Bibliothek die ich von Extern bekommen habe zu simulieren bzw. den Memory Leak.
    Ich kann mit der orginalen Bibliothek hier auf meiner Couch nicht arbeiten/testen da ich dafür die Kamera brauch. Die Kamera liegt auf Arbeit und ich will/darf sie nicht mit nach Hause nehmen.


    @mrMo

    Danke für den Tipp, ich habe auch gleich versucht den Vorschlag umzusetzen. Es funktioniert soweit. Ich muss jetzt nur noch einen Weg finden wie ich beim automatisierten Neustart eine Aktion auslöse.
    Bei dem automatischen Neustart sollte dann ohne Bediener der "Start" Button gedrückt werden, damit die Kamera dann wieder verbunden wird und das Messprogramm startet.
    Entweder übergebe ich einen Start Parameter oder versuche es mit Sendmessage , wobei ich mit Sendmessage anscheinend schon wieder an eine Grenze gestoßen bin. Ich bekomme nicht den Button Handle ermittelt.
    Er ist leider immer NULL. Laut Internet FindWindowEx(iHwndForm, IntPtr.Zero, "button", "Text auf dem Button") sollte nur der Text auf dem Button als String übergeben werden.

    Hat jemand einen Rat wie es wirklich aussehen sollte?

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
    3. Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWnd1 As IntPtr, ByVal hWnd2 As IntPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As IntPtr
    4. Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    5. Private Const BM_CLICK As Integer = &HF5
    6. Private iHwndForm As IntPtr
    7. Private iHwndButton As IntPtr
    8. Dim proc As Process
    9. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    10. proc = Process.Start("C:\Users\Bernd\source\repos\Memory\Memory\bin\Release\Memory.exe")
    11. End Sub
    12. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    13. proc.CloseMainWindow()
    14. End Sub
    15. Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
    16. iHwndForm = proc.MainWindowHandle 'proc.Handle
    17. iHwndButton = FindWindowEx(iHwndForm, IntPtr.Zero, "button", "Start_Button")
    18. SendMessage(iHwndButton, BM_CLICK, 0, 0)
    19. End Sub
    20. End Class



    Freundliche Grüße
    sITrAiN

    Vollzitat entfernt. ~Thunderbolt

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

    @sITrAiN Unterlass das vollständige Zitieren von Posts über Dir, das ist hier unerwünscht.
    Du kannst einen Fehler einer DLL nicht simulieren, wenn Du ihn nicht präzise kennst.
    Wenn Du dann nämlich die DLL nimmst, kommt wahrscheinlich was ganz anderes raus.
    Und glaub mir, ich habe genug mit Kameras und solch zu tun gehabt, das Dispose() und ggf. ein GC.Collect() danach wirken Wunder.
    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
    Ok, werde das Zitieren unterlassen.

    Es gibt leider keine Dispose() bei der Funktion womit ich mir die Bilder abhole. Hier mal ein Beispiel wie ich ein Bild abhole.

    Allein der Aufruf von filter.getImageBufferSafe("RET") erhöht mir den Speicher. In Summe sind das ca. 10MB in einer Stunde bei 10FPS.
    Der Speicher läuft auch ohne das erstellen des Bildes im Backroundworker hoch.


    VB.NET-Quellcode

    1. Dim Daten As IntPtr
    2. Daten = filter.getImageBufferSafe("RET")
    3. If BRW_RET.IsBusy = False Then BRW_RET.RunWorkerAsync(Daten)


    Dieses Sub rufe ich auch zyklisch im Programm auf.

    VB.NET-Quellcode

    1. Public Sub FlushMemory()
    2. GC.Collect()
    3. GC.WaitForPendingFinalizers()
    4. If (Environment.OSVersion.Platform = PlatformID.Win32NT) Then
    5. SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, 100, 400)
    6. End If
    7. End Sub


    Was zwar den Arbeitssatz(kb) Speicher bzw. Privat(kb) Speicher verringert aber nicht den zugesicherten Speicher. Der steigt dann um die besagten 10MB die Stunde an.
    Der zugesicherte Speicher stößt dann irgendwann an seine Grenze.

    Freundliche Grüße
    sITrAiN

    sITrAiN schrieb:

    bei der Funktion womit ich mir die Bilder abhole
    Die Bilder selbst musst Du dieposen, oder, falls es über einen IntPtr kommt, per Marshal.FreeHGlobal(IntPtr).
    In welcher Form genau kommt das Bild bei Dir an?
    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!
    Zunächstmal wäre wasserdicht abzusichern, obs anne Dll liegt oder an falscher Verwendung derselben.
    Dein TestCode aus post#1 hat selbst ein mögliches MemoryLeak - da werden lauter Bilder, und Process-Instanzen erzeugt und nicht disposed.



    Und die "böse" Dll heisst wirklich ClassLibrary1?
    Na wer seine Dlls so benennt - da wunderts mich nicht, wenn da wirklich MemoryLeaks drinne sind (anders wärs eiglich verwunderlicher ;) ).

    Das sinnvollste wäre, ihn aufzufordern, sein Produkt zu korrigieren - ist das ausgeschlossen, und wenn ja, warum ?

    Ansonsten, wenn man wirklich damit leben muss, dann heissts: "Hässliche Workarounds" (alias "Grütze auf Grütze stapeln").



    Ich hab mal aufgeschnappt, dass man Dlls in AppDomains laden kann, und die AppDomains kann man auch wieder entladen.
    Aber keine Ahnung, ob das hilft, ob das Leak innerhalb von sone AppDomain bleibt oder Domain-Übergreifend.



    Ansonsten eben wirklich aus- und wieder anmachen. Evtl. ist Application.Restart() dein Froind.



    Aber beides wirklich hässlich, und verletzt den Grundsatz: "Finger weg von instabilen Bibliotheken!"

    Herumgehampel mit GC.Collect() und Zeugs kannste dir sparen.
    Also kann sein, dasses hilft, aussagekräftigere Werte zur Speicherauslastung zu bekommen, aber ansonsten versaut das nur die Performance, ohne am Leak was zu verbessern.
    Tatsächlich haben .Net-Programme einen vielfach überhöhten Speicherverbrauch, aber das ist nur "virtuell", und wird freigegeben, wenn tatsächlich andere Prozesse den Memory benötigen.
    Also Speicher-Auslastungs-Anzeige ist ein höcht unzuverlässige Indikator.
    Allerdings dass das Prog iwann abstürzt, ist denn doch sehr deutlich.
    Und ja, eine Tendenz kann man auch ablesen - wenn der SpeicherFrass sich ununterbrochen immer erhöht, ohne sich je einzupegeln - das nix gutt.
    @RodFromGermany

    Die Bilder bekomme ich als IntPtr, danach wandele ich sie in ein PNG mit folgender Funktion um.
    Aber wie gesagt auch ohne das erstellen des eigentlichen Bildes läuft der Speicher voll.

    VB.NET-Quellcode

    1. Private Sub BRW_RET_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BRW_RET.DoWork
    2. BRW_RET.ReportProgress(1)
    3. Dim Temp_RETBitmap As Bitmap
    4. RETDatasource = BitmapSource.Create(Size_Image.xSize, Size_Image.ySize, 96, 96, PixelFormats.Bgra32, Nothing, CType(e.Argument, IntPtr), bufferSize, stride)
    5. Using outStream As New IO.MemoryStream
    6. Dim Encoder As PngBitmapEncoder = New PngBitmapEncoder
    7. Encoder.Frames.Add(BitmapFrame.Create(RETDatasource))
    8. Encoder.Save(outStream)
    9. Temp_RETBitmap = New Bitmap(outStream)
    10. End Using
    11. e.Result = Temp_RETBitmap
    12. Temp_RETBitmap.Dispose()
    13. End Sub


    Ich werde es am Montag mal mit deinem Tip versuchen:

    VB.NET-Quellcode

    1. ​Marshal.FreeHGlobal(Daten)


    oder so

    VB.NET-Quellcode

    1. Marshal.FreeHGlobal(filter.getImageBufferSafe("RET"))



    Freundliche Grüße
    sITrAiN
    @sITrAiN Eigentlich müsste in der Beschreibung der DLL stehen, wie Du die Bilder aufzuräumen hast. :/
    Oder Du solltrest mal den Support dort bemühen. Es ist undenkbar, dass Du der erste bist, der dieses Problem hat.
    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!

    ErfinderDesRades schrieb:

    ClassLibrary1

    sITrAiN schrieb:

    Du hast Recht, aber das möchte ich ja nicht. Ich habe diese Class Bilder gebaut um die erwähnte Bibliothek die ich von Extern bekommen habe zu simulieren bzw. den Memory Leak.
    Sportlich, dasser die DLL zu simulieren versucht.
    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 habe versucht einen Memeory Leak damit zu erzeugen um darauf reagieren zu können, um dann herauszufinden wie ich damit am besten umgehe.
    Und ich glaube zum Testen ist die Namensgebung nicht unbedingt das Nonplusultra, dem Papierkorb ist es egal.


    Es gibt leider keine Beschreibung der Dll, da die Kamera nicht von der Stange ist. Die Dll ist noch im Beta Stadium, die Problematik mit dem Fehler ist bekannt bei ihm. Eine Übergangslösung wäre schön gewesen da er noch andere Projekte hat, daraufhin mein Anliegen an die hier ansässige Community. Die Aufgabenstellung wurde anscheinend nur von einem hier erkannt. Das die Dll nicht perfekt ist muss ich erstmal hinnehmen und damit klar kommen. Das Modell der Kamera lässt sich nicht ändern da es die einzige ist die solche Bilder erstellt und die dazugehörigen Messwerte ausgibt. Begonnen haben wir mit einer Konsolenanwendung die Bilder in einen Ordner ablegt, diese habe ich dann eingelesen und dargestellt und ausgewertet. Aber wie ihr vielleicht aus eigener Erfahrung wisst, ist manches nicht so einfach und es muss alles wachsen und wir lernen daraus.

    sITrAiN schrieb:

    Ich habe versucht einen Memeory Leak damit zu erzeugen um darauf reagieren zu können, um dann herauszufinden wie ich damit am besten umgehe.
    Das kann natürlich nix bringen (denke ich). Natürlich kannst du in deiner Ersatz-Dll ieinen MemoryLeak provozieren.
    Aber das bietet ja keinerlei Hilfe zum wirklichen Problem.
    Also egal, ob du mich zu deinen Verstehern oder Nicht-Verstehern zählst: 2 Workaround-Ansätze habe ich dir gegeben - täte empfehlen, dich damit auseinanderzusetzen.
    @sITrAiN Kanst Du mit den Leuten zusammenarbeiten, das Leak zu definieren und zu schließen?
    Permanent um das Framework drumherum zu programmieren um Fehler von anderen zu verschleiern ist suboptimal.
    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!