Laden...

using Gültigkeitsbereich und Dispose() Unterschiede ?

Erstellt von scrabbl vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.661 Views
S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren
using Gültigkeitsbereich und Dispose() Unterschiede ?

Hallo,

zur Vorgeschichte:
Ich habe eine Windoof Form, welche einer Klasse eine List übergibt. Besagte Klasse erzeugt ein Excel Objekt, füllt es mit dem Inhalt der List, bastelt ein hübsches Diagram, speichert es auf die Platte und feuert dann ein Event für die Form dass das Bildchen nun bereit ist.
Die Form fügt daraufhin das Bild einer pictureBox hinzu. Hier die Eventbehandlung:

        public void drawGraphIfReady(object sender, EventArgs e)
        {
            graphPic = new Bitmap(Application.StartupPath + "\\graphic.gif");
            this.pictureBoxGraph.Image = graphPic;
            this.Refresh();
            graphExists = true;
        }

Soweit so gut, nur irgendwann wird das Programm ja mal beendet und da sollte das Bildchen auf der Platte auch mit verschwinden.
Vor dem System.IO.Delete kommt natürlich ein Dispose(), sowohl auf mein Bitmap als auch auf meine pictureBox. Dennoch wirft er beim Löschen eine Exception dass das Bild bereits benutzt wird...
Wenn ich mein Event von oben aber so schreibe:

        public void drawGraphIfReady(object sender, EventArgs e)
        {
            using(Bitmap graphPic = new Bitmap(Application.StartupPath + "\\graphic.gif"))
            {
                  this.pictureBoxGraph.Image = graphPic;
                  this.Refresh();
                  graphExists = true;
            }
        }

Dann kann ich mein Bildchen ganz normal löschen, ohne Probleme.
Dafür wirft er mir an anderen Stellen im Programm dann ständig System.ArgumentException mit ungültigem Parameter in der System.Drawing.dll, was ja logisch ist, das Bitmap Objekt ist ja gleich wieder weg nach dem using Block.

Laut MSDN macht der using Block am Ende doch auch nix anderes als ein Dispose() ? Wieso geht es auf die eine Weise, nicht jedoch auf die andere ?

Gelöschter Account
vor 13 Jahren

Vor dem System.IO.Delete kommt natürlich ein Dispose(), sowohl auf mein Bitmap als auch auf meine pictureBox. Dennoch wirft er beim Löschen eine Exception dass das Bild bereits benutzt wird...

das problem ist, das das systemhandle auf die datei da noch nciht freigegeben ist.

Dann kann ich mein Bildchen ganz normal löschen, ohne Probleme.
Dafür wirft er mir an anderen Stellen im Programm dann ständig System.ArgumentException mit ungültigem Parameter in der System.Drawing.dll, was ja logisch ist, das Bitmap Objekt ist ja gleich wieder weg nach dem using Block.

gut erfasst. du siehst das das keine lösung ist.

zeig mal, wie du die datei löschen möchtest (die methode) und wo genau du das durchführst.

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Freigegeben und gelöscht wird innerhalb des ClosingEvents meiner Form, also quasi wenn es beendet wurde:

private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
{
       if (loadedSuccessfull && edcLogbuch.Count > 16)
       {
              this.pictureBoxGraph.Dispose();
              graphPic.Dispose();
              System.IO.File.Delete(Application.StartupPath + "\\graphic.gif");
       }
}

Erst gebe ich quasi frei was die pictureBox verwendet und danach das Bitmap selber, war eigentlich der Meinung das sei alles. Woanders wird das ja nicht verwendet, dachte ich zumindest.

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

So, nachdem ich die letzte 3Stunden die tiefen des Microsoft Supports durchforstet hat hab ich nun rausgefunden dass das Freigeben des Bildes gar nicht so leicht geht. In einem dotnet MVP Bereich hab ich 2 Workarounds gefunden die das Sperren der Datei umgehen, falls es noch jemand interessiert oder mal jmd über Google mit dem selben Problem hier drauf stößt, hier die 2 Möglichkeiten:

Man erzeugt ein Bitmap mit dem Bild auf der Platte, erstellt aus diesem Bitmap ein zweites und gibt das Erste direkt wieder frei und arbeitet ab dann mit der Kopie.

Bitmap originalPic = new Bitmap(filePath);
Bitmap tempPic = new Bitmap(originalPic);
originalPic.Dispose();

//Irgendwas mit dem Bild anstellen

tempPic.Dispose();

So ist die Datei auf der Platte nicht gesperrt und kann gelöscht werden.

Die 2te Möglichkeit besteht darin das Bild von der Platte in einen MemoryStream zu lesen und das Bitmap im Programm dann aus dem Stream zu erstellen. Auch so wird die Datei auf der Platte nur einmal gelesen aber nicht gesperrt.

System.IO.FileStream fStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open);
System.IO.BinaryReader bReader = new System.IO.BinaryReader(fStream);
System.IO.MemoryStream mStream = new System.IO.MemoryStream(bReader.ReadBytes(Convert.ToInt32(fStream.Length)));
bReader.Close();
Bitmap graphic = new Bitmap(mStream);

Wichtig ist hier nur das der Stream erst geschlossen wird wenn das Bild wirklich nirgends mehr benötigt wird.
Hab beide Wege getestet, funktionieren bei mir beide, auch wenn ich immer noch der Meinung bin das es auch anders gehen müsste.

3.971 Beiträge seit 2006
vor 13 Jahren

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Argh das hät ich mal vor 4h gebraucht, aber da ich das Diagramm mit Excel mache bin ich nicht auf die Idee gekommen mit GDI+ zu suchen 😦

Vlt wäre das ja noch ein interessantes FAQ Thema das man darin verlinken könnte (also der Link von Eichhörnchen, der ist viel ausführlicher), weil so ein Problem tritt sicher öfter mal auf. Ist aber nur ein Vorschlag 😃

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo scrabbl,

das wird zwar immer wieder mal gefragt, aber nicht oft genug für die FAQ.

herbivore

61 Beiträge seit 2009
vor 13 Jahren
Using <> Dispose()

Hallo,

das Problem ist zwar gelöst, aber hier ein kurzer Überblick zu dem Unterschied und wichtigen Punkten bei Using und Dispose() - was ich selber bisher auch festgestellt habe.

Der Vorteil von Dispose
(Das Dispose wirft zum Glück nie eine Exception, egal wie oft es aufgerufen wird.)

  • Man kann festlegen, wann genau das Dispose ausgeführt werden soll und die Ressourcen freigegeben werden sollen

Nachteile bei Dispose() - (ohne Vergleich zu Using)

  • bei einem Stream kann es vielleicht sein, dass Daten verloren gehen, da man Close verwenden sollte um die Daten in die Datei zu schreiben
    (i.d.R. ist Dispose aber immer sinnvoll)

Bei Using ist eines Wichtig
Wenn eine Exception innerhalb des Using-Blocks auftritt, so habe ich festgestellt, wird das Dispose nicht mehr aufgerufen.
Also am besten immer ein Try-Catch-Block im Using machen.
Das im Using verwendete Objekt bitte nicht einem anderen Objekt zuweisen, denn wenn man z.B. ein Bild einer PictureBox zuweist, dann muss unweigerlich ein Fehler auftreten beim Verlassen des Using-Blocks oder das Bild würde sofort wieder verschwinden.

Vorteil bei einem Using-Block

  • alle deklarierten Variablen innerhalb des Blocks verfallen, wenn der Using-Block verlassen wird
  • durch den Using-Block kann man einen klaren Bereich definieren in dem dieses Objekt ausschließlich verwendet wird
  • beim Verlassen des Blocks wird immer die Dispose-Methode aufgerufen

Soweit dazu 😉

In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.

Gelöschter Account
vor 13 Jahren
  • Man kann festlegen, wann genau das Dispose ausgeführt werden soll und die Ressourcen freigegeben werden sollen

das definiert der using-scope auch.. daher ist das kaum ein vorteil . was ein vorteil ist, das man keinen scope braucht und das dispose auch zu einem sehr viel späteren zeitpunkt geschehen kann.

allerdings bringt es auch den aufwand, das die verwendende klasse selbst IDisposable implementieren sollte.

bei einem Stream kann es vielleicht sein, dass Daten verloren gehen, da man Close verwenden sollte um die Daten in die Datei zu schreiben
(i.d.R. ist Dispose aber immer sinnvoll)

deswegen sind ja auch die streamklassen so implementiert, das sie bei einem dispose auch das close aufrufen. wenn das nicht so implementiert ist, ist der implementierer schuld und nciht dispose.

Wenn eine Exception innerhalb des Using-Blocks auftritt, so habe ich festgestellt, wird das Dispose nicht mehr aufgerufen.

das ist falsch.
using ist ja explizit dafür da um im falle einer exception das dispose sicher zu stellen. using ist ncihts anderes als ein try-finally

Also am besten immer ein Try-Catch-Block im Using machen.

das ist sinnfrei.

Das im Using verwendete Objekt bitte nicht einem anderen Objekt zuweisen, denn wenn man z.B. ein Bild einer PictureBox zuweist, dann muss unweigerlich ein Fehler auftreten beim Verlassen des Using-Blocks oder das Bild würde sofort wieder verschwinden.

das ist korrekt. sobald der scope verlassen wird, wird dispose aufgerufen. in dem beschriebenen falle muss das dispose manuell erfolgen... mit allen sich daraus ergebenden folgen (IDisposable implementation)

alle deklarierten Variablen innerhalb des Blocks verfallen, wenn der Using-Block verlassen wird

das ist so nicht ganz korrekt. c# erlaubt den zugriff nciht aber generell sind sie dennoch da. in der IL werden variablen immer am anfang jeder methode angelegt.
scopes sind ein sprachfeatures der höheren sprachen.

61 Beiträge seit 2009
vor 13 Jahren

Okay, danke für diese schnelle Antwort und Korrektur.

Ich habe etwas aus meiner unvollständigen Erinnerung geplaudert und es vorher nicht geprüft. Nun bin ich jetzt auch dadurch schlauer geworden 😉

Hoffentlich beanrwortet irgendwann jemand auch meine Eigentliche Frage so schnell ^^

In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Das im Using verwendete Objekt bitte nicht einem anderen Objekt zuweisen, denn wenn man z.B. ein Bild einer PictureBox zuweist, dann muss unweigerlich ein Fehler auftreten beim Verlassen des Using-Blocks oder das Bild würde sofort wieder verschwinden.

das ist korrekt. sobald der scope verlassen wird, wird dispose aufgerufen. in dem beschriebenen falle muss das dispose manuell erfolgen... mit allen sich daraus ergebenden folgen (IDisposable implementation)

Und genau deshalb hab ich ja das Thema aufgemacht, weil es so einfach nicht zu sein scheint. Wenn ich, wie in meinem Fall, einer pictureBox ein Bild von der Platte zuweisen will und es bei Programmende von der Platte löschen möchte, wirds schon blöd:

Bitmap pic = new Bitmap(filePath);
pictureBox1.Image = pic;

//Programm läuft

//Programm Ende
pictureBox1.Image.Dispose();
pic.Dispose();
System.IO.File.Delete(filePath);   //PENG

2 manuelle Dispose(), sowohl auf das pictureBox-Objekt als auch das Bitmap-Objekt, so dass das File eigtl defintiv freigegeben sein müsste, dennoch kann man es nicht löschen.

Benutze ich einen using Block (auch wenn er hier wenig Sinn macht, da es dann jede Menge anderer Exceptions geben wird, aber ist ja nur zur Verdeutlichung):

using(Bitmap pic = new Bitmap(filePath))
{
     pictureBox.Image = pic;

//Programm läuft

//Programm Ende
}
System.IO.File.Delete(filePath);  //Alles wunderbar

Wieso ist das File auf der Platte nach dem using Block komplett freigegeben so das es gelöscht werden kann, nach den händischen Dispose() aber nicht ?
Da muss doch am Ende des using Blocks noch was anderes passieren als nur ein Dispose() aufs Bitmap, weil sonst würde es im ersten Beispiel doch auch gehen.

Gelöschter Account
vor 13 Jahren

der code von dir funktioniert bei mir wunderbar.... hast du evtl noch woanders einen stream auf die datei offen?

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Ja, solang die PictureBox die ganze Zeit angezeigt ist geht es bei mir auch.

Ich habe ein TabControl und auf einem Tab sitzt die PictureBox. Wenn ich das Bild lade und danach das Tab mit dem Bild offen lasse, dann geht es. Wähle ich ein anderes Tab aus (nachdem das Bild geladen wurde) dann geht es auch noch.

Sobald ich aber ein anderes Tab auswähle und dann wieder das Tab mit dem Bild, ab dann geht es nicht mehr. Scheinbar erstellt er da dann mehr handles auf das file ? Wüsste aber nicht wieso und vorallem was für welche...

Mit dem using Block geht es immer, der scheint also alle handels zu schließen.

Gelöschter Account
vor 13 Jahren

Mit dem using Block geht es immer, der scheint also alle handels zu schließen.

using macht wirklich ncihts anderes als ein dispose aufruf... kannst du mal eine minimalistische anwendung bauen, wo man das problem reproduzieren kann und diese dann hier posten?

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

So, hab ein kleines Programm zusammengebastelt mit dem man den Fehler hätte reproduzieren können, aber wie ichs ja fast befürchtet habe tritt er nicht auf.

Hab aus meinem Projekt nur rauskopiert was relevant ist, da es dann plötzlich ging musste der Fehler wohl in dem Codebereich liegen den ich nich mitkopiert hab.

Und nach etwas suchen bin ich jetzt an meinem EventHandler hängen geblieben. Da ich selber ein Event geschrieben habe welches die Form benachrichtigt wann das Diagramm fertig ist und auf der Platte liegt.

Zusammen hiermit:

Managed memory leaks are caused by unused objects remaining alive by virtue of unused or forgotten references. A common candidate is event handlers—these hold a reference to the target object (unless the target is a static method).

D.h. also das der EventHandler eine Referenz auf das Objekt hält welches das Bild erzeugt hat. Kann dies der Grund sein das es nach wie vor gesperrt ist ?

Gelöschter Account
vor 13 Jahren

so ganz ist mir noch nciht klar, wie der genaue aufbau bei dir ist... evtl kannst du ja genau das in form eines reproduzierenden projektes gießen?

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Hab ein kleines Beispielprojekt gemacht.
Ist im Grund vollkommen simpel, du hast 2 Tabs, im ersten stehen (in dem Fall) Beispielzahlen und im 2ten dann die Grafik.

Allerdings musst du Office installiert haben, weil es Excel benutzt um die Grafik zu erzeugen. Hoffe hab jetzt auf die Schnelle nix vergessen, habs nur kurz zusammengepappt 😃

PS: In meinem Programm sind das natürlich keine Zufallszahlen sondern werden aus einer Binärdatei gelesen und anhand einer Struktur aufgearbeitet, aber das tut ja weniger zur Sache. Wichtig ist ja nur wie das Diagramm erzuegt und verwendet wird.

Gelöschter Account
vor 13 Jahren

also ich kann den fehler nciht nachvollziehen. ich bekomme nur eine nullreferenceexception, wenn das chart noch garnicht geladen wurde, und ich die form beende. ansonsten funktioniert alles tadellos.

auch vom programmablauf her scheint alles korrekt zu sein.

S
scrabbl Themenstarter:in
211 Beiträge seit 2010
vor 13 Jahren

Oh stimmt, die Abfrage nach der Grafik beim beenden hab ich in der Eile vergessen.

Das ist aber seltsam...

Vlt ist ja auch irgendwas in meinem VS falsch konfiguriert, muss ich mal noch schauen.