2010-04-27 11 views
6

Natürlich sollten wir Dispose() auf IDisposable-Objekte aufrufen, sobald wir sie nicht brauchen (was oft nur der Bereich einer "using" -Anweisung ist). Wenn wir diese Vorsichtsmaßnahme nicht ergreifen, können schlimme Dinge von subtil bis hin zum Anhalten passieren.Gibt es eine Zeit, IDisposable.Dispose zu ignorieren?

Aber was ist mit "der letzte Moment" vor Prozessbeendigung? Wenn Ihre IDisposables zu diesem Zeitpunkt noch nicht explizit entsorgt wurden, ist es nicht wahr, dass es nicht mehr zählt? Ich frage, weil nicht verwaltete Ressourcen unterhalb der CLR durch Kernel-Objekte repräsentiert werden - und die Win32-Prozessbeendigung wird sowieso alle nicht verwalteten Ressourcen/Kernel-Objekte freigeben. Anders gesagt, keine Ressourcen bleiben "geleakt", nachdem der Prozess beendet wurde (unabhängig davon, ob Dispose() bei der Verweildauer von IDisposables aufgerufen wurde).

Kann jemand an einen Fall denken, in dem eine Prozessbeendigung immer noch eine ausgelaufene Ressource hinterlassen würde, einfach weil Dispose() nicht explizit für ein oder mehrere IDisposables aufgerufen wurde?

Bitte missverstehen Sie diese Frage nicht: Ich versuche nicht, IDisposables zu ignorieren. Die Frage ist nur technisch-theoretisch.

EDIT: Und was ist mit Mono unter Linux? Ist Prozessabbruch dort genauso "zuverlässig" bei der Bereinigung von nicht verwalteten "Lecks"?

LATE EDIT: Obwohl "andere Verwendungen" für IDisposables vorhanden sein können, liegt mein Fokus ausschließlich auf Ressourcenlecks. Ich habe zwei Antworten gehört: (1) Wenn Ihr Prozess sich weigert, zu beenden, haben Sie ein Leck und (2) ja, Ressourcen können sogar auslaufen, selbst wenn der Prozess endet. Ich stimme sicherlich mit Punkt (1) überein, obwohl es gerade außerhalb des Umfangs dessen, was ich suche, liegt. Ansonsten ist Punkt (2) genau das, wonach ich suche, aber ich kann das Gefühl nicht abschütteln, dass es nur eine Vermutung ist. Jeffrey Richter ("Windows via C/C++") erklärt, dass ein (ordnungsgemäß) beendet Win32-Prozess keine durchgesickerten oder verwaisten Ressourcen verlassen wird. Warum sollte ein Prozess, der die CLR enthält, dies ändern? Wo ist die Dokumentation, das spezifische Beispiel oder das theoretische Szenario, das der Idee, dass die Win32-Prozessbereinigungsfunktion bei der Verwendung der CLR gefährdet ist, Glaubwürdigkeit verleiht?

+0

Der Finalizer ist immer da, um sich um nicht verwaltete Ressourcen zu kümmern, die nicht entsorgt wurden. Und wenn es nicht läuft, hast du das Betriebssystem, um den Schrapnell zu säubern. –

Antwort

2

Technisch gesehen hängt alles davon ab, was IDisposable leistet. Es wurde für viele Dinge verwendet, nicht nur für nicht verwaltete Ressourcen.

Zum Beispiel, wenn ich an einer Outlook-Anwendung arbeitete, hatte ich eine nette kleine Abstraktion der Outlook-API gebaut. Attachments waren besonders lästig, um mit Streams zu arbeiten, weil Sie sie in einer temporären Datei speichern, damit arbeiten und dann aufräumen mussten.

So sah meine Abstraktion etwas wie folgt aus:

OutlookMailItem mailItem = blah; 
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) { 
    // work with stream 
} 

Wenn Entsorgen auf dem AttachmentStream genannt wurde, die Temperatur wurde auf Basis von wurde gelöschte Datei. Wenn in diesem Fall Dispose nicht aufgerufen wird, wird die temporäre Datei niemals bereinigt. Ich hatte einen Prozess beim Start, um nach diesen verwaisten Dateien zu suchen, aber ich dachte, ich würde dies als Beispiel präsentieren.

In Wirklichkeit werden fast alle IDisposable-Implementierungen, die eine Art Socket, Handle oder Transaktion umhüllen, einfach vom Betriebssystem nach der Prozessbeendigung bereinigt. Aber offensichtlich ist das wie Wohlfahrt. Vermeide es, wenn du kannst.

+0

Einverstanden, normalerweise sollten wir uns nicht auf das Wohlergehen der Win32-Prozessbeendigung verlassen. Aber Sie scheinen zuzustimmen, dass die Beendigung des Win32-Prozesses alle verbleibenden Ressourcen freigibt, die IDisposables * hätte bearbeiten sollen. Ich fange an zu denken, dass dein Beitrag meine Antwort ist. Gedanken? –

+0

Es gibt wirklich zwei Teile zu dieser Frage. Die erste beinhaltet den Rahmen. Es wird nicht garantiert, dass Dispose aufgerufen wird, aber es ist der beste Versuch. Der zweite Teil Ihrer Frage ist, was passiert, wenn die Finalizer nicht ausgeführt werden. Und das ist, wo "es abhängt". Windows hat Bereinigung von Griffen und dergleichen eingebaut. Wenn das IDisposable eine dieser Ressourcen umschließt, bereinigt Windows unabhängig davon, was der GC getan hat. Aber wenn das IDisposable etwas Seltsames tut (wie in meinem Beispiel, eine temporäre Datei zu löschen), ist es möglich, dass die Bereinigung niemals stattfinden wird. – Josh

1

Eine Sache, die ich oft entsorgt, sind serielle Ports-- jetzt, während eine serielle Schnittstelle freigegeben werden sollte, wenn das Programm nachlässt, können andere Programme nicht auf die serielle Schnittstelle zugreifen, während es von einem anderen Prozess gehalten wird. Also, wenn Ihr Prozess sich weigert zu sterben, dann binden Sie eine serielle Schnittstelle. Das kann sehr schlecht sein, wenn der Benutzer versucht, Ihr Programm neu zu starten, aber eine Zombie-Version des vorherigen Prozesses hält immer noch an der seriellen Schnittstelle fest.

Und ja, ich hatte mein Programm finden sich selbst in einem Zombie-Zustand vor, und dann beschweren sich die Kunden, dass das Programm nicht mehr funktioniert, weil das Programm nicht an die serielle Schnittstelle beim Neustart anschließen. Das Ergebnis besteht darin, den Benutzer entweder durch einen Prozess im Task-Manager zu töten oder ihn neu starten zu lassen, was keine besonders benutzerfreundliche Aufgabe darstellt.

+1

Verwenden Sie nicht Invoke() in der DataReceived-Ereignisbehandlungsroutine, da es anfällig für das Deadlock des Close() - Aufrufs ist. Verwenden Sie stattdessen BeginInvoke(). –

+0

Ich bin mir ziemlich sicher, dass die Zombifizierung anderswo passiert; Ich verwende den DataReceived-Ereignishandler nicht, da das Gerät, an das ich anschließe, flockig ist und kein ordnungsgemäßes Stoppzeichen aufweist. Stattdessen sendet es eine Prüfsumme des Befehls als Stoppzeichen, und da die Prüfsumme immer unterschiedlich ist, muss ich die Zeichenfolge jeweils ein Zeichen aufbauen. Es ist quälend und klar von jemandem in Hardware entworfen, nicht von Software. – mmr

0

Sie schreiben Ihren benutzerdefinierten Code, um Objekte in Ihrer Einwegklasse freizugeben. Sie müssen Code schreiben, um nicht verwalteten und verwalteten Code freizugeben.

Deshalb brauchen wir Dispose Funktion. Die gesamte Speicherfreigabe erfolgt nicht automatisch, wie Sie es im Fall des nicht verwalteten Codes gesagt haben.

Wenn Sie jetzt denken, dass nicht verwalteter Code automatisch vom Betriebssystem freigegeben wird, ist das nicht wahr. Es gibt viele Handles und Sperren, die möglicherweise aktiv bleiben, wenn sie nicht ordnungsgemäß von Ihrer Anwendung entsorgt werden.

0

Zunächst möchte ich darauf hinweisen, dass IDisposable nicht nur für Windows-Kernel-Objekte oder nicht verwalteten Speicher ist. Vielmehr ist es für Dinge, die die Garbage-Collection nicht versteht (Kernel-Objekte sind ein Sonderfall).

Ein kleines Beispiel, nur um Ihnen eine Vorstellung zu geben:

sealed class Logger : IDisposable 
    { 
     private bool _disposed = false; 

     public Logger() 
     { 
      System.IO.File.AppendAllText(@"C:\mylog.txt", "LOG STARTED"); 
     } 

     ~Logger() 
     { 
      Dispose(); 
     } 

     public void Dispose() 
     { 
      if (!_disposed) 
      { 
       System.IO.File.AppendAllText(@"C:\mylog.txt", "LOG STOPPED"); 
       _disposed = true; 
      } 
     } 

     public void WriteMessage(string msg) 
     { 
      System.IO.File.AppendAllText(@"C:\mylog.txt", "MESSAGE: " + msg); 
     } 
    } 

Beachten Sie, dass diese Logger Klasse nicht „halten“ keine Kernel-Objekt-Stil-Ressourcen. Es öffnet die Datei und schließt sie sofort. Es gibt jedoch diese Logik dahinter, dass "LOG STOPPED" geschrieben werden sollte, wenn das Objekt zerstört wird. Diese Logik ist etwas, das GC nicht verstehen kann - und das ist der Ort, an dem man IDisposable verwenden kann.

Daher können Sie nicht darauf zählen, dass Windows nach Ihnen Kernel-Objekte bereinigt. Es könnte immer noch etwas geben, das selbst Windows nicht kennt.

Wenn jedoch Ihre Wegwerfobjekte richtig geschrieben sind (d. H. Dispose im Finalizer aufrufen), werden sie beim Beenden des Prozesses dennoch von der CLR entsorgt.

Also, die Antwort ist: ja, es ist nicht notwendig, Dispose kurz vor dem Projekt zu verlassen. Aber NICHT, weil Einweg-Ressourcen Kernel-Objekte sind (weil sie nicht unbedingt sind).


bearbeiten

Als Josh Einstein zu Recht darauf hingewiesen, Finalizers sind garantiert eigentlich nicht auf Prozessbeendigung laufen. So dann ist die Antwort wird: immer Dispose aufrufen, um nur

+0

"sie werden immer noch von der CLR bei Prozess-Exit entsorgt" Finalizer werden nicht garantiert bei Prozessbeendigung laufen und wenn es nur wenige von ihnen gibt und sie schnell beenden, ist es sehr wahrscheinlich, dass sie unterbrochen werden, wenn überhaupt überhaupt angerufen werden. – Josh

+0

Einige unterstützende Aussagen zusätzlich zu meinem obigen Kommentar gefunden. Die Timeouts sind länger als ich dachte, aber die CLR wird immer noch Finalizer-Timeouts auferlegen, was besonders bemerkenswert wäre, wenn IO im Finalizer ausgeführt würde. http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html – Josh

+0

Ja, du hast Recht, das habe ich komplett vergessen. –

2

Während einer kooperativen Abschaltung klagen zu werden, wird die AppDomain entladen, die alle Finalizers verursacht auszuführen:

Von Object.Finalize Dokumentation:

Beim Herunterfahren einer Anwendungsdomäne wird Finalize automatisch für Objekte aufgerufen, die nicht von der Finalisierung ausgenommen sind, auch für solche, auf die noch zugegriffen werden kann.

So sind Sie beim Herunterfahren sicher, solange zwei Kriterien erfüllt sind:

  • Jedes IDisposable Objekt, das noch lebt hat einen richtig implementierter Finalizerthread (true von Klassen-Framework kann nicht wahr sein von weniger vertrauenswürdigen Bibliotheken); und

  • Es ist eigentlich ein kooperatives Herunterfahren, und nicht ein abnormales Herunterfahren wie eine harte Prozessbeendigung, Ctrl-C in einer Konsolen-App oder Environment.FailFast.

Wenn eines dieser beiden Kriterien nicht erfüllt sind, ist es möglich, dass Ihre Anwendung auf globalen nicht verwalteten Ressourcen hält (wie ein Mutex), die tatsächlich auslaufen. Daher ist es immer besser, Dispose früh zu rufen, wenn Sie können. Most der Zeit, können Sie sich auf die CLR und Objekt Finalisten verlassen, um diese Arbeit für Sie zu tun, aber besser als Nachsicht.

+0

Nur ein Vorbehalt - Finalizer werden nicht garantiert während der Prozessbeendigung aufgerufen. Die CLR wird einen Versuch unternehmen, sollte aber nicht erwartet werden. Das aktuelle Verhalten hat Zeitlimits pro Finalizer und für die allgemeine Beendigung. Dieser Beitrag hat einige unterstützende Aussagen: http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html – Josh

+0

@Josh: Sehr wahr. Natürlich ist nichts garantiert. Wenn Sie den Stecker ziehen, wird * nichts * gereinigt. ;) – Aaronaught

+0

Zusätzlich zu Joshs Kommentar oben gibt es Umstände, unter denen * keine * Finalizer aufgerufen werden, wie zum Beispiel das Aufrufen von 'System.Environment.FailFast' (das ist im Grunde das .Net-Äquivalent von kill -9 auf * nix). –

Verwandte Themen