BEARBEITEN Weitere Informationen finden Sie unter Bearbeiten der Notiz am Ende der Frage.
Ursprüngliche Frage
Ich habe eine CacheWrapper-Klasse, die intern eine Instanz der .NET MemoryCache
-Klasse erstellt und festhält.
MemoryCache
hakt sich selbst in AppDomain-Ereignissen ein, sodass es niemals zu einer Sammlung von Garbage-Collected-Daten kommt, es sei denn, es wird explizit entsorgt. Sie können dies mit dem folgenden Code überprüfen:
Aufgrund des Verhaltens glaube ich, dass meine MemoryCache-Instanz als nicht verwaltete Ressource behandelt werden sollte. Mit anderen Worten, ich sollte sicherstellen, dass es im Finalizer von CacheWrapper angeordnet ist (CacheWrapper ist selbst. Disposable folgt dem Standard Dispose (bool) -Muster).
Ich stelle jedoch fest, dass dies Probleme verursacht, wenn mein Code als Teil einer ASP.NET-App ausgeführt wird. Wenn die Anwendungsdomäne entladen ist, wird der Finalizer auf meiner CacheWrapper-Klasse ausgeführt. Dies wiederum versucht, die MemoryCache
-Instanz zu beseitigen. Hier stoße ich auf Probleme. Es scheint, dass Dispose
versucht, einige Konfigurationsinformationen von IIS zu laden, was fehlschlägt (vermutlich, weil ich gerade dabei bin, die App-Domäne zu entladen, aber ich bin mir nicht sicher. Hier ist der Stack-Dump, den ich habe:
Gibt es dafür eine bekannte Lösung? Habe ich Recht damit, dass ich das MemoryCache
im Finalizer ablegen muss?
BEARBEITEN
Dieser Artikel validiert Dan Bryants Antwort und bespricht viele der interessanten Details. Insbesondere deckt er den Fall von StreamWriter
ab, der ein ähnliches Szenario wie meins gegenübersteht, da es seine Puffer nach der Entsorgung ausspülen will. Hier ist, was der Artikel sagt:
Grundsätzlich können Finalizer nicht auf verwaltete Objekte zugreifen. Die Unterstützung für die Abschaltlogik ist jedoch erforderlich einigermaßen komplexe Software. Der Windows.Forms-Namespace behebt dies mit Application.Exit, die einen ordnungsgemäßen Herunterfahren initiiert. Wann Entwerfen von Bibliothekskomponenten, ist es hilfreich, einen Weg zu haben unterstützende Shutdown-Logik integriert mit den bestehenden logisch ähnlich IDisposable (dies vermeidet die Definition eines IShutdownable Schnittstelle ohne eingebaute Sprachunterstützung). Dies wird in der Regel durch ordnungsgemäßes Herunterfahren unterstützt, wenn IDisposable.Dispose wird aufgerufen, und ein fehlgeschlagener Shutdown wird ausgeführt nicht. Es wäre noch besser, wenn der Finalizer dazu verwendet werden könnte ordentliche Herunterfahren wann immer möglich.
Microsoft ist auch auf dieses Problem gestoßen. Die StreamWriter-Klasse besitzt ein Stream-Objekt; StreamWriter.Close spült seine Puffer und Rufen Sie dann Stream.Close auf. Wenn ein StreamWriter jedoch nicht geschlossen wurde, wird Der Finalizer kann seine Puffer nicht löschen. Microsoft "löste" dieses Problem durch Ich gebe StreamWriter keinen Finalizer, in der Hoffnung, dass dies Programmierer tun werden Beachten Sie die fehlenden Daten und leiten Sie ihren Fehler ab. Das ist perfekt Beispiel für die Notwendigkeit der Abschaltlogik.
Alles in allem, denke ich, dass es möglich sein sollte, "Managed Finalization" mit WeakReference zu implementieren. Im Grunde genommen muss Ihre Klasse eine WeakReference für sich selbst registrieren und eine Aktion mit einer Warteschlange abschließen, wenn das Objekt erstellt wird. Die Warteschlange wird dann von einem Hintergrund-Thread oder -Timer überwacht, der die entsprechende Aktion aufruft, wenn die gepaarte WeakReference gesammelt wird. Natürlich müssen Sie darauf achten, dass Ihre Finalize-Aktion nicht versehentlich die Klasse selbst festhält und somit die Sammlung verhindert!
Sie können verwaltete Objekte im Finalizer nicht löschen, da sie möglicherweise bereits abgeschlossen wurden (oder, wie Sie hier gesehen haben, Teile der Umgebung möglicherweise nicht mehr in dem erwarteten Zustand sind.) Dies bedeutet Wenn Sie eine Klasse enthalten, die explizit disponiert werden muss, muss Ihre Klasse auch explizit disponiert werden. Es gibt keine Möglichkeit zu "schummeln" und die Entsorgung automatisch zu machen. Leider ist die Speicherbereinigung in solchen Fällen eine undichte Abstraktion.
Ich würde vorschlagen, dass Objekte mit Finalizern im Allgemeinen nicht der Außenwelt ausgesetzt sein sollten und nur starke Verweise auf Dinge enthalten sollten, die tatsächlich zur Fertigstellung benötigt werden, und denen nichts in der Außenwelt ausgesetzt ist, das sie nicht erwartet zu diesem Zweck verwendet werden. Public-Facing-Typen sollten selbst keine Finalizer haben, sondern stattdessen Cleanup-Logik in privat gehaltene Instanzen finalisierbarer Klassen kapseln, deren Zweck darin besteht, solche Logik einzukapseln.
Das einzige Mal, wenn es wirklich Sinn macht, dass ein Finalizer versucht, eine Ressource zu bereinigen, die einem anderen Objekt gehört, ist das andere Objekt designed für die Schnittstelle zum Finalizer. Ich kann mir keine Orte vorstellen, an denen Framework-Klassen in die richtigen Hooks eingearbeitet haben, aber ich werde ein Beispiel geben, wie Microsoft sie dafür geschaffen haben könnte.
Ein File
-Objekt könnte ein Ereignis mit threadsicheren Subskribier- und Abmeldemethoden anbieten, die ausgelöst würden (Benachrichtigung des letzten Subskribenten zuerst), wenn das Objekt File
entweder einen Dispose
-Aufruf oder eine finalize
-Anforderung erhält. Das Ereignis würde zwischen dem Zeitpunkt, an dem Finalize
aufgerufen wird, und dem Zeitpunkt, an dem die gekapselte Datei tatsächlich geschlossen wird, ausgelöst und könnte von einer externen Pufferklasse als Signal verwendet werden, das dem File
jede Information geben muss, die es erhalten hat aber noch nicht weitergegeben.
Beachten Sie, dass es für die korrekte und sichere Ausführung einer solchen Sache erforderlich ist, dass der Teil des Objekts File
, der den Finalizer enthält, nicht der Öffentlichkeit zugänglich gemacht wird und dass eine lange schwache Referenz verwendet wird Wenn es ausgeführt wird, während das öffentliche Objekt noch am Leben ist, wird es sich selbst für die Finalisierung neu registrieren. Beachten Sie, dass wenn die einzige Referenz auf ein WeakReference
-Objekt in einem finalisierbaren Objekt gespeichert wird, die Target
-Eigenschaft ungültig wird, wenn das finalisierbare Objekt finalisiert werden kann , selbst wenn das tatsächliche Ziel der Referenz noch aktiv ist . Defektes Design, IMHO, und eines, das etwas vorsichtig umgangen werden muss.
Es ist möglich, Objekte mit Finalizern zu gestalten, die kooperieren können (der einfachste Weg, dies zu tun, ist, nur einen Gegenstand in der Gruppensportart zu finalisieren), aber wenn die Dinge nicht mit Finalizern zusammenwirken sollen, wird die Das Beste, was man tun kann, ist, dass ein Finalizer einen Alarm ausgibt, der anzeigt: "Dieses Objekt sollte nicht Dispose
d sein, war es aber nicht; weil es nicht war, werden Ressourcen auslaufen und es gibt nichts zu tun darüber hinaus beheben Sie den Code, um das Objekt in Zukunft ordnungsgemäß zu entsorgen. "
Tags und Links c# asp.net finalizer idisposable memorycache