Seit dem ich jetzt was mit C# mache, bin ich nun schon des öfteren
über die Begriffe verwalteter (managed) und unverwalteter (unmanaged) Code gestolpert.
Bisher habe ich es immer so hin genommen - richtig verstanden hab ichs aber
noch nicht. 🤔
Als ich aktuell das Dispose-Entwurfsmuster einsetzten wollte, wurde ich nun erneut
damit konfrontiert X(.
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Freigeben von verwalteten Resourcen
}
// Freigeben von unverwalteten Resourcen
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Base()
{
Dispose (false);
}
Nun habe ich mir versucht vorzustellen, welche Resourcen es geben könnte,
die lediglich dann freigeben werden, wenn der Finalizer aufgerufen wird nicht aber das Dispose.
Auf diese Weise erhoffte ich auch dem Unterschied zwischen un- bzw. verwaltet auf die Schliche zu kommen.
Aber irgendwie ist mir da nix eingefallen ?(.
Kann mir vielleich jemand konkrete Beispiele nennen, wie so eine Dispose-Methode aussehen könnte? =)
Ressourcen sind:
* Windows (für Fenster, Fonts, Brushes, etc.)
* Datenbanklverbindungen
* Filehandles
* COM-Objekte
und noch einige andere mehr, die mir grad nicht einfallen.
Mit verwaltet/unverwaltet hat das allerdings niocht direkt zu tun. Verwalteter Code erlaubt keinen direkten Zugriff mehr auf den Speicher (der Speicherzugriff wird vollständig von .NET "verwaltet"). In C/C++ muss sich der Programmierer selbst um den Speicher kümmern, er "verwaltet" ihn selbst. "Verwalteter Code" meint also "automatisch verwalteter Code".
Da die Ressourcenfreigabe in verwaltetem Code eine besondere Herausforderung ist (das macht man ja sonst im Destruktor, den es in verwalteten Sprachen nicht gibt...), hängen die Themen natürlich doch zusammen.
Ein sehr interessantes Thema. Weiß jemand zufällig verständliche deutsche Quellen?
Danke 😉
Original von svenson
Verwalteter Code erlaubt keinen direkten Zugriff mehr auf den Speicher (der Speicherzugriff wird vollständig von .NET "verwaltet"). In C/C++ muss sich der Programmierer selbst um den Speicher kümmern, er "verwaltet" ihn selbst. "Verwalteter Code" meint also "automatisch verwalteter Code".
Aber wird dann nicht alles automatisch verwaltet, solange ich mich innerhalb der
.NET-Grenzen bewege? Was wären denn unverwaltete Resourcen?
In meinem konkreten Fall habe ich innerhalb der Klasse, die IDisposable implementiert eine private Form-Klasse.
Diese möchte ich über Dispose freigeben können. Dies würde ja dann in etwas so aussehen:
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Freigeben von verwalteten Resourcen
this.form.Dispose();
}
// Freigeben von unverwalteten Resourcen
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Base()
{
Dispose (false);
}
Oder? Allerdings würde das form.Dispose() ja dann nicht aufgerufen werden, wenn das Dispose(false) vom
Finalizer aufgerufen wird. Wäre es aber nicht in beiden Fällen sinnvoll die Resourcen der Form freizugeben?
Die Ressourcen werden von .NET schon verwaltet (innerhalb verwalteter Objekt), aber da der Garbage Collector aus Anwendungssicht nicht deterministisch arbeitet, kannst du die Ressourcenfreigabe nur dann gezielt aussteuern, wenn du sie selbst in die Hand nimmt (also selbst verwaltet). Daher das Dispose()-Geraffel.
In der allermeisten Anwendungen ist die automatische Freigabe von Ressourcen durch .NET ausreichend, in bestimmten Szenarien sind aber die Ressourcen zu knapp bemessen, um sie einer automatischen und daher "zufälligen" Speicherfreigabe zu überlassen.
Um dein Beispiel zu ziteren: Die explizite Freigabe von Form-Objekten ist unnötig.
Windows kann hunderte (vermutlich aus Tausende) von Fenstern anzeigen. Ende ist nur, wenn die Zahl der Fensterhandles ausgeht (Zehntausende) oder der Speicher knapp wird. Aber du musst schon verdammt viele Fenster erzeugen um an diese Grenze zu geraten. Wenn du also in 1000 Fenster erzeugst, dann wieder zumachst und 10 sec später wieder 1000 Fenster aufmachst, dann kann man sich schon überlegen, dass man die einzelnen Fenster explizit disposed.
Dies ist aber ein rein akademischer Anwendungsfall. Man hat NIEMALS so viele Fenster (ich kenne zumindest keine Anwendung), also braucht man sich um die Freigabe von Fenstern NIEMALS zu kümmern (es sei denn sie allokieren andere knappe Ressourcen...).
Merke: Dispose() macht Sinn, wo etwas KNAPP ist. Ist nichts knapp, kein Dispose() (auch hier gibt es Ausnahmen, z.B. wo bestimmte Freigabereihenfolgen eine Rolle spielen, aber das sei mal aussen vor, weil noch seltener).
svenson generell macht es aber doch Sinn, den Speicher immer wieder freizugeben wenn ich mit meiner Operation fertig bin siehe StreamWriter, Form, etc...
Irgendwann wird der StreamWriter vom GC abgebaut. Ich schätze mal ein paar Sekunden nachdem die Referenz weg ist.
Die Freigabe von Filehandles erfolgt innerhalb des Close() automatisch, d.h. machst du das File zu (sollte man machen, da sonst u.U. kein anderer zugreifen kann), dann hast du Freigabe.
Wenn die Freigabe hier nicht mit mit dem Schliessen der Datei verbunden wäre, könnte man guten Gewissens drauf verzichten, Windows kann auch eine Menge offener Filehandles verwalten. 🙂
Verwalteter Code nimmt einem Arbeit ab. Wenn du Forms freigibst, dann wird deine Anwendung vermutlich keinen Deut besser laufen, als wenn du es läßt, aber mit hoher Wahrscheinlichkeit instabiler. Du hast auch mehr Code schreiben müssen. Ich kann da keinen Vorteil erkennen.
Ich will mal so sagen: Dispose() schadet oft nicht, kann aber in nebenläufigen Szenarien durchaus Probleme bereiten (DisposeExceptions), wie hier im Forum nachzuvollziehen ist. Bei Forms ist das aufgerund des asynchronen Message-Handlings der Fall.
Streams sind ionsofern ein schechtes Beipsiel, da hier die Ressourcenfreigabe implizit mit einer Operation geschieht (Close), man hat gar keine Wahl.
Daher ist die Aussage "zur Sicherheit Dispose machen ist besser" FALSCH. Das Gegenteil gilt: Dispose nur im "Notfall". Man sollte Dispose nur dort einsetzen, wo man genau den Objekt-Lebenzyklus selbst im Griff hat oder genau kennt. Bein Windows-Form möchte ich behaupten, dass die meisten das nicht wissen. Daher möglichst KEINE explizite Freigabe, es sei denn es wird ausdrücklich angeraten oder vorgeschrieben. Bei Forms ist das nicht der Fall.
Anders sieht es z.B. bei GDI-Objekten oder so aus. Da wird in der Hilfe explizit auf die Freigabe hingewiesen und das using() angemahnt. Dem sollte man folgen.
Ich weiss, es ist für C/C++/Delphi-Coder ist das alles schwer nachzuvollziehen, aber die Entlastung des Users von der Speicherfreigabe durch eine GC wurde nicht umsonst eingeführt. Es ist nachgewiesen, dass eine GC die Stabilität von Anwendungen erhöht, deshalb hat sich dieses Konzept auch durchgesetzt und GC-Plattformen verdrängen nach und nach Sprachen wie C/C++. Vor 10 Jahren haben noch die meisten Leute in C/C++ gecodet, heute sind es nur noch 10-15%. der Anteil wird weiter zurückgehen, da die Anwendungsfälle, in denen Ressourcenknappheit herrscht und der GC-Vorteil nicht ausfährt, zunehmend weniger werden. Selbst Handys können heute mit Java oder .NET, vor Jahren undenkbar. Dieser Trend ist unaufhaltbar.
Also verabschiedet euch schonmal von Free() und Delete(). 🙂
...aber mit hoher Wahrscheinlichkeit instabiler...
Ich weiß genau wenn mein Dialog-Fenster nicht mehr benötigt wird. Nämlich dann wenn die Save-Methode darin aufgerufen wurde und danach die Close-Methode. Das was du schreibst wiederspricht sich meiner Meinung nach manchmal. Leider kenne ich mich damit nicht gut genug aus und bin auch nicht alter C'ler, aber kann man sowas irgendwo dokumentiert nachlesen?
Naja, die Diskussion ist alt wie man jederzeit nachgoogeln kann. Mag sich jeder seine eigene Meinung bilden. Ich persönlich ziehe gerne Nutzen aus der Existenz eines GC, wer das nicht möchte, dem steht ja Dispose() frei.
Wenn man die expliztie Freigabe als Feature betrachtet, handelt man sich aber in jedem Fall ein Wartungsproblem ein, da die Allokation und Freigabe sauber symmetrisch implementiert sein müssen. Symmetrie bedeutet aber immer Codeabhängigkeit und erschwert die Relokation von Code.
Code mit Dispose() ist daher generell kompexer (auch irgendwie logisch). Wenn es also keinen Nutzen gibt (keine Knappheit), ist Dispose-Code also schlechter. QED.
Dem gegenüberstellen könnte man höchstens die Wiederverwendung von Code. Schreibe ich Code im Szenario A und verwende KEIN Dispose und verwenden den Code im Szenario B wieder, wo Knappheit existiert, dann muss ich Dispose() nachträglich einbauen. Nachteil. Dies abzuschätzen ist aber m.E. einfach, so dass sich an der Gesamtaussage nichts ändert. Ist ein generelles Design-Problem der Angemessenheit.
MS hält sich zu dem Thema raus und gibt keinerlei Empfehlungen....
Hier übrigens ein guter Artikel zum Thema:
http://www.gotdotnet.com/team/libraries/whitepapers/resourcemanagement/resourcemanagement.aspx
Eine dritte Meinung innerhalb der Diskussion um Dispose() ist die völlige Abschaffung von Dispose und die ausschließliche Verwendung von symmetrischen Methoden ( Open <-> Close) und des Finalizers. Ein Meinung, der ich mich allerdings nicht anschliessen kann, sofern eine Anwendung nicht mehrere unabhängige Ressourcen hält. Hier wird der Einsatzt von Dispose() zum "alles abräumen" fragwürdig, da Zustände auftreten können, die für den Nutzer einer Klasse nicht transparent sind.
hmm... Und wie ist das ganze dann im Compact Framework? Dort entwickle ich nämlich und zur Einführung wurde mir von den Leuten gesagt, dort immer ein Dispose() auszuführen wo möglich, da es ansonsten zu üblen Problem führen kann (mal geht was und dann wieder nicht). Die Leute dir mir das sagten, haben wohl auch den Speicher beim Ablauf solcher Anwendungen analysiert und haben festgestellt, dass dieser ohne Dispose() teilweise recht schnell volllaufen kann.
Gut, diese Aussage basiert, so wie ich das einschätze, nicht unbedingt auf einem fundierten Hintergrundwissen. Soll heissen jene Leute haben eben schnell mal versucht eine Lösung für das Problem zu finden, dass es eben des öfteren so komische Fehler gab, das manchmal was hing. Und ob die generelle Verwendung des Dispose() dann tatsächlich das Problem an der Wurzel bekämpft scheinen die auch nicht zu wissen. Zumindest die auftauchenden Speicherprobleme waren damit wohl so ziemlich verschwunden gewesen.
Mein Vorgabe hiess also:
public const bool compact_framework__immer_Dispose_benutzen_wo_moeglich = true;
Es blieb mir nix anderes übrig als dies von anfang an gehorsam zu befolgen und hatte bisher eigentlich noch keine grösseren Probleme mit dem CF gehabt. Die Anwendungen bei denen ich das Dispose verwendete, hatten dabei v.a. von Streams, Forms und SqlServerCe gebraucht gemacht.
naja 🤔
Wie ich ja schrieb, hängt der "Sinn" von Dispose() vom Umfeld ab.
Bin ich in einer "knappen" Umgebung, wie auf einem PDA, dann werden vermutlich MEHR Dispose() auftauchen, als in einem normalen .NET-Code. Ist doch keine Frage.
Die Frage ist nur, ob man deswegen JEDEN Komfort, den der GC bietet fahren läßt und Dispose() grundsätzlich aufruft wo es möglich ist (IDisposable implementiert). Gibt einige Leute die das befürworten!
Ich dagegen sage: Wenn ich weiss, dass etwas automatisierbar ist, warum soll ich es nicht tun? Wenn ich WEISS, dass es keine Krise gibt, brauche ich keinen Krisenplan (und das ist Dispose).
Beim Thema Speicher kann man auch entspannt sein, da der "Gewinn" durch Dispose ja nur zeitlich ist, d.h. der Speicher wird (max. ein paar Sekunden) früher freigegeben. Meist unkritisch wo nicht viele Objekte in kurzer Zeit allokiert werden.
Dazu kommt: Dispose() verursacht bei nicht sachgemäßer Verwendung Probleme: Rufe ich Dispose() auf einer Referenz auf und habe woanders eine zweite Referenz auf das Objekt, dann gibt es lustige Effekte... alles Probleme, die ein GC adressiert und ausschließt.
Dispose() ist schon gar nix für Anfänger. Die sollten erstmal dem GC vertrauen und nur in "Notfällen" (Ressourcen absehbar knapp) Dispose() einsetzen.
Hallo zusammen,
da Dispose ja nicht den Speicher eines Objekt selbst freigibt, sondern "nur" die nicht-verwalteteten Ressourcen, ist/wäre Dispose bei Objekten, die keine nicht-verwalteten Ressourcen benutzen, eine Null-Operation. Demzufolge ist Dispose auch in vielen Klassen gar nicht implementiert. Dispose hat insofern auch nichts direkt mit dem GC zu tun und ersetzt ihn erst recht nicht. Der Zusammenhang ist wenn überhaupt dann umgekehrt: Wenn der GC zuschlägt, ruft er bevor er den Speicher des Objekts selbst freigibt den Destruktor (=Finalize) auf und in Klassen, die IDisposable implementieren, ruft der Destruktor typischerweise Dispose auf.
Sollte man Dispose nur explizit aufrufen oder darauf warten bis der CG den Destruktor und dieser Dispose aufruft?
Auch wenn ein explizites Dispose - wie sevenson schreibt - seine Probleme mit sich bringt, so bin ich doch der Meinung, dass ein explizites Dispose immer angebracht ist (unter explizites Aufrufen rechne ich auch using, was sogar noch besser ist). Wenn ich z.B. eine Datei zum Schreiben öffne und später dieselbe Datei zum Lesen öffne, so will ich nicht davon abhängig sein, ob der nicht-deterministische GC die Datei in der Zwischenzeit schon geschlossen hat oder nicht. Analog gilt das für die Wiederverwendung jeder Ressource, nicht nur für Dateien. Außerdem ist es bei knappen Ressourcen wünschenswert die Zeit des Zugriffs zu minimieren - und ich denke der GC läuft keineswegs mit nur wenigen Sekunden Verzögerung. Im Gegenteil, wenn der Speicher nicht knapp wird, läuft der GC u.U. für Stunden nicht.
herbivore
Einigen wir uns einfach darauf, dass es Sinn macht Dispose() zu verwenden um Ressourcen zu schonen aber mit Vorsicht in Multi-Threading-Umgebungen einzusetzen ist.
Das GC-Problem mit der fehlenden Steuerung (wann springt er an) ist ein gesondertes, welches ja gleichermaßen (oder noch schlimmer) den verwalteten Speicher selbst betrifft. Hier habe ich Null Kontrolle mangels Dispose(). Nur die Collect()-Kanone. Gäbe es hier bessere Steuerungsmöglichkeiten, stellt sich das Dispose-Thema entspannter dar. Genauer: Dispose ist deswegen hier nur ein Thema, WEIL der GC im Background-Mode läuft und so implementiert ist wie er ist (erst laufen wenn nix mehr geht).
Zudem: Seit den HT-Systemen kann man aber den GC ganz beruhigt in den Vordergrund-Modus schalten. Mit den neuen Dual-Cores wird es wohl eh Standardeinstellung werden. Der Background-Mode ist doch eher eine Krücke um den Umsteigern das Gefühl zu geben, dass der GC nix kostet (tut er auch nicht wenn er nicht anspringt), frei nach dem Motto: Hoffen wir dass der User die Anwendung beendet bevor der Speicher alle ist... was ja meist sogar stimmt. 🙂
In der jetzigen Konstellation sollte man vielleicht tatsächlich eine wenig "verschwenderischer" mit Dispose umgehen als in Zukunft. Nichsdestotrotz ist aus Sicht der "reinen Lehre" Dispose() eher zu vermeiden, weil es sich nur aus technischen Limitierungen (GC-Implementierung und .NET-Unterbau) ableitet aber die Architektur verschlechtert.
Wenn ich eine GC-Plattform habe, aber wegen des Hybrid-Charakters der FCL-Klassen quasi für jedes Objekt Dispose brauche, dann habe ich nichts gewonnen und kann gleich weiter in C++ coden. Statt der doppelten Freigabe von Speicher (C++) fliegen mir nun ObjectDisposedExceptions um die Ohren (wie neulich in meinem PDA-Projekt, wie ich auch intensiv Dispose einsetze 🙂 ). Aus Sicht der seperation of concerns ist Dispose sowieso ein Drama. Interessant wäre eine Ressourcenverwaltung via AOP und Lebenszykluszustand, damit könnte man wenigstens einige der jetzigen Fallstricke ausräumen und den gewünschten Automatismus wieder einführen. Warum nicht die Ressourcenfreigabe automatisch durchführen, wenn die letzte Referenz verschwindet. Die erst anschließend erfolgende Speicherfreigabe hat - wie gesagt - ja gar nix mit der Ressourcenfreigabe zu tun, es ist eigentlich nur eine Frage des Scopes.
Was es in C# gebraucht hätte, um mit dem Problem umzugehen ist weniger das Dispose-Pattern, sondern eine Delegate, der beim Verschwinden der letzten Referenz auf dem Objekt aufgerufen wird, wo das Objekt aber noch Gültigkeit besitzt.
Hallo svenson,
dem Einigungsvorschlag stimme ich gerne zu.
Grundsätzlich würde ich es auch begrüßen, wenn Objekte zerstört werden, sobald die letzte Referenz verschwindet. Das wäre praktischer und der GC wäre dann deterministisch. Dass bei zyklischen Verweisstrukturen jedoch die letzte Referenz nie verschwindet, ist vermutlich der Hauptgrund für den jetzigen GC.
Aber selbst wenn man die zyklischen Verweisstrukturen in den Griff bekäme und Objekte immer schnellst möglich aufgeräumt werden würden, käme es mir doch merkwürdig vor eine Datei zu schließen, in dem ich schreibe: filestream = null;
Da ist mir filestream.Close ();
doch sympathischer. Zumal mit filestream = null;
die Datei ja auch nur geschlossen werden würde, wenn filestream die letzte Referenz darauf ist!
Und dass man (knappe/bestimmte) Ressourcen explizit freigeben muss/sollte, bleibt ja bestehen. Die Lebensdauer einer Variablen stimmt ja auch nicht zwangsläufig mit der gewünschten Lebensdauer der Ressource überein. Eine automatische Ressourcenfreigabe alleine reicht dann wieder nicht.
herbivore