Laden...

Problem bei MemoryStream.Dispose()

Letzter Beitrag vor 16 Jahren 14 Posts 3.065 Views
Problem bei MemoryStream.Dispose()

Moinsen Community,

ich habe eine quasi Wrapper-Klasse für das Image Object geschrieben da ich meine grafischen Objekte gerne selber handle.

Um grafische Objekte nun zu speichern wandele ich das eigentliche Image-Object in einen Base64-String um. Und beim laden eben wieder zurück. Um die Speicherung zur Laufzeit durchzuführen benutze ich einen MemoryStream.

Lade-, und "Speicher"-Methoden:


private MemoryStream memory = null;

        public void convertFromBase64( string base64 )
        {
            memory = new MemoryStream( Convert.FromBase64String( base64 ) );
            Image img = Image.FromStream( memory );

            base.Size = new SizeF( img.Size.Width, img.Size.Height );
            base.setCoords();
            img.Dispose();
        }

        public string convertToBase64()
        {
            string base64 = Convert.ToBase64String( memory.ToArray() );
            return base64;
        }

Um nun, wenn mein Img(Image) Objekt nicht mehr benötigt wird die Resourcen wieder freizugeben die von dem MemoryStream-Objekt benutzt wurden, führe ich die folgende Methode aus:


public void Dispose()
        {
            if (this.memory != null)
            {
                this.memory.Close();
                this.memory.Dispose();
                this.memory = null;
                
            }
        }

Leider wird der Speicher nicht freigegeben und wenn man dann so zu sagen immer und immer wieder das gleiche Bild öffnet obwohl es geschlossen wurde. Naja ich sags mal so irgendwann stößt jeder Rechner an die Grenzen seiner Speicherkapazitäten.

Kann mir jemand erklären warum er die Resourcen nicht frei gibt oder was ich dazu noch zusätzlich ausführen muss.

Zusatz: Durch einen Debug weiß ich definitiv das er in die Dispose()-Methode geht beim schließen und das auch das MemoryStream objekt dabei nicht null ist und das Programm den Körper des if-Statements behandelt.

Gruß
Maurice

Die drei Tugenden eines Programmierers:
Faulheit, Ungeduld und Hochmut!

obwohl mir schleierhaft ist wozu du da runkonvertierst, um ein image zu speichern (es gibt ja auch in Imge.Save und ebenso ein load) und es dazu noch größer machst als es vermutlich ist (base64 string?) solltest du wissen, das der gc nur dann aufräumt, wenn es notwendig wird. hast du allerdings einen größeren bereich durch dispose freigegeben und solltes es eher die ausnahme sein, das du es benötigst, kann man ien gc.Collect(...) verwenden. vorsicht aber mit dem einsatz, da dies einiges an rechenkraft kostet und eigendlich nciht aufgerufen werden sollte, da es mit den normalen ablauf des gc kollidieren kann.

Ich konvertiere rum, da ich das Image in einer Xml Struktur zustätzlich zu anderen Daten speichere. Und ich benötige alle Daten in einer Datei um Benutzermanipulationen an der Dateistruktur besser entgegen kommen zu können. (Was schon vorkam das es nette Layer 8 Probleme gab und es dann hieß, dass das Prog daran schuld ist das ja gelösche Bilder nicht mehr geladen werden können)

Eine andere Lösung wäre noch das ich das Image-Objekt serialisiert in der Datei zu den anderen Daten speichere und je nach Methode serialisiere respektive deserialisiere.

Danke schonmal werde mal versuchen das dann direkt mit dem GC auszumachen ... mal sehen ob ich mich mit ihm einig werde 🙂.

Gruß
Maurice

Die drei Tugenden eines Programmierers:
Faulheit, Ungeduld und Hochmut!

Wie stellst Du denn fest, das der Speicher nicht freigegeben wird? Irgendwo muss es ja jetzt ein Problem geben wenn der GC das bei seiner normalen Arbeit nicht schafft.

Über den TaskManager ... ich sehe mir an was das Programm so an Speicher alloziert wenn ich das Bild öffne und da sich beim Schließen die Speicherallozierung nicht ändert .... kann ich davon ausgehen das auch der MemoryStream nicht "gekillt" wird aus dem Speicher.

Die drei Tugenden eines Programmierers:
Faulheit, Ungeduld und Hochmut!

binäre serialisierung direkt in dein memory-stream wäre für ein bild das passender meiner meinung nach, da man selbst mit einem base64-string nichts an den bildern effektiv manuell ändern kann.

kann ich davon ausgehen das auch der MemoryStream nicht "gekillt" wird aus dem Speicher

ah...
finger weg vom gc. er macht schon seine sache. wenn der speicher voll läuft dann räumt er auf. das ist die reguläre vorgehensweise. freier speicher nützt dir eh nichts....

Hallo mbk_chilli,

mit dem Taskmanager kannst du den relevanten Speicherverbrauch nicht verlässlich ermitteln. Ich würde davon ausgehen, dass in deinem Code bezüglich der Freigabe alles getan ist, was zu tun ist und dass der Speicher spätestens dann freigegeben wird, wenn er für etwas anders gebraucht wird. Vertraue dem GC.

In Prinzip tust du sogar mehr als nötig. MemoryStream.Dispose ruft sowieso nur MemoryStream.Close auf. Das Close (oder wahlweise auch das Dispose) kannst du dir also sparen.

herbivore

Ist ja nur ein Teil des Codes, sieht aber komisch aus. convertFromBase64 ist non-static. Dispose wiederum ein Member von Image. memory ebenfalls ein Member. Ich vermute mal, hier stimmt irgendwas mit dem Scope nicht. Hast du mal mit dem Debugger den Lauf verfolgt?

CconvertFromBase64 muss hier non static sein, weil es auf den non static Member zugreift.

Woran siehst Du, das Dispose ein Member von Image ist und nicht von der Klasse, die auch die convertFromBase64 enthält ?

Woran siehst Du, das Dispose ein Member von Image ist und nicht von der Klasse, die auch die convertFromBase64 enthält ?

Vermutung, denn:

img.Dispose();

und nicht

Dispose();

Hrm, habe das wie folgt gelöst, bin gerade an etwas ähnlichem dran nur in einem anderen Zusammenhang.


    private string ConvertToBase64(Bitmap bmp)
    {
      byte[] array = null;
      using (MemoryStream stream = new MemoryStream())
      {
        bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
        stream.Position = 0; // rewind
        array = new byte[stream.Length];
        stream.Read(array, 0, (int)stream.Length);
      }
      return Convert.ToBase64String(array);
    }

    private Image ConverToImage(string str)
    {
      Image img = null;
      using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(str)))
      {
        img = Image.FromStream(stream);
      }
      return img;
    }

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

Woran siehst Du, das Dispose ein Member von Image ist und nicht von der Klasse, die auch die convertFromBase64 enthält ?

Vermutung, denn:

img.Dispose();  

und nicht

Dispose();  

Man wird in der eigenen Instance wohl sehr selten Dispose aurufen. Zudem: Wie würdest Du denn von Image die Methode Dispose ändern ?

Wo ich Dir allerdings recht gebe: Die Implementierung schaut merkwürdig aus. Da ist die Implementierung von Andreas doch viel viel besser 😮)

Die using-Kiste ist fein. Ansonsten: Wenn du sehen willst, wieviel Heap .NET verwaltet muss du den Performance-Monitor anwerfen, bzw. Tools wie den CLRProfiler. Der TaskManager gibt dir diese Infos nicht. Grundsätzlich gibt .NET einmal allokierten virtuellen Speicher (verwendet für den .NET-Heap) nur dann frei, wenn der Speicher voll ist. Gerade bei Bilder kann das schnell passieren, weil diese i.d.R. im Large Object Heap landen, der nicht defragmentiert wird. Durch Fragmentierung des LOH wird .NET dann gezwungen mehr und mehr virtuellen Speicher anzufordern. Wird die Speichergrenze erreicht, gibt .NET nicht mehr verwendete Speicherseiten an Windows zurück.

Wow, rege Diskussion.

Vertraue dem GC.

Na dann werde ich das mal tun.

Zur Struktur:

Dispose ist vom Interface IDisposable in der "Img" Klasse implementiert und das mehr aus leichteren Verständnisgründen.

Diese Methode soll nur dafür sorgen das der MemoryStream nicht mehr im Speicher alloziiert ist.

Die ConvertTo* Methoden sind ebenfalls in dem "Img" Objekt und werden von meiner Speicher/Lade-Klasse aufgerufen.

Das "img.Dispose()" verweist nicht auf einen internen Member sondern ruft von einem temporären .NET-Image Objekt die Dispose Methode auf.

Ich bin derzeitig dabei die Speicheroutinen anzupassen und es mal mit dem XMLSerializer zu probieren. Wobei das Problem dadurch nicht behoben wird, nicht wirklich, da ich auch den MemoryStream für den Serializer verwende.

Aber ein wenig rumprobieren kann nicht schaden.

PS: Eigentlich will ich nur Layer-8 Probleme in meinem Prog ausschalten.

Gruß
Maurice

Die drei Tugenden eines Programmierers:
Faulheit, Ungeduld und Hochmut!