.NET-Server-Garbage Collection und Objektlebensdauer

8

Ich und ein Kollege haben unterschiedliche Meinungen darüber, wann ein Objekt in .NET gesammelt werden kann. Nimm den folgenden Code:

%Vor%

Mein Kollege behauptet, dass wenn ein Release-Build unter Verwendung des Server-Garbage-Collectors ausgeführt wird, dass das Objekt request nach dem Aufruf von request.Stream als Garbage Collection-Objekt gültig ist. Er behauptet, dass dies nur mit dem Garbage Collector des Servers und niemals mit dem Garbage Collector der Arbeitsstation geschieht.

Der Grund dafür ist, dass die Klasse Request über einen Finalizer verfügt, der die für die Anforderung angegebene Stream schließt. Wenn DoStuff2 verwendet wurde, um den Stream zu verwenden, wurde daher die Ausnahme "object dises" ausgelöst. Da der Finalizer nur vom Garbage Collector ausgeführt werden kann, sagt mein Kollege, dass eine Garbage-Collection vor dem Ende des finally-Blocks, aber nach der letzten Verwendung von request

stattgefunden haben muss

Ich glaube jedoch, dass der obige Code nur eine Abkürzung für etwas wie dieses ist:

%Vor%

Dann kann request nach dem Aufruf von request.Stream nicht als Garbage Collected gesammelt werden, da es immer noch vom Block finally aus erreichbar ist.

Wenn der Garbage Collector das Objekt auch sammeln könnte, würde der finally Block möglicherweise undefiniertes Verhalten aufweisen, da Dispose für ein GC'd Objekt aufgerufen würde, was keinen Sinn ergibt. Ebenso ist es nicht möglich, den Block finally zu optimieren, da eine Ausnahme in den Block try / using geworfen werden könnte, bevor eine Garbage-Collection stattgefunden hätte, die die Ausführung des Blocks finally erfordern würde.

Wenn das Schließen des Streams im Finalizer ignoriert wird, ist es für den Garbage Collector immer möglich, das Objekt vor dem Ende des Blocks finally zu sammeln und die Logik im Block finally zu optimieren?

    
Sean 05.12.2014, 12:51
quelle

2 Antworten

10

In dieser Frage geht ziemlich viel los, also werde ich zuerst die übergreifenden Probleme angehen.

  1. Die Variable, die in einer using -Anweisung deklariert wurde, wird nicht vor dem Ende des Blocks gesammelt, genau aus dem Grund, den Sie angegeben haben - ein Verweis wird gehalten, um Dispose() im impliziten% co_de aufzurufen % block.

  2. Wenn Sie in C # einen Finalizer schreiben, tun Sie wahrscheinlich etwas falsch. Wenn Ihr Finalizer in C # finally aufruft, tun Sie definitiv etwas falsch. Außerhalb einer Implementierung von .NET selbst habe ich Hunderte von missbrauchten Finalizern gesehen und genau 1 Finalizer , der tatsächlich benötigt wurde. Weitere Informationen finden Sie unter DG Update: Dispose, Finalisierung und Ressourcenverwaltung .

  3. Stream.Dispose() hat nichts mit der Finalisierung zu tun. Diese Ausnahme tritt im Allgemeinen auf, wenn Code ObjectDisposedException für ein Objekt aufruft (nicht finalisiert) und dann ein späterer Aufruf ausgeführt wird, um etwas mit dem Objekt zu tun.

    Manchmal ist es nicht offensichtlich, wenn Code von Dispose() ablegt. Ein Fall, der mich überraschte, war die Verwendung von Stream als Teil des Sendens eine HTTP-Anfrage mit StreamContent . Die Implementierung ruft HttpClient nach dem Senden der Anfrage auf, also musste ich einen Wrapper schreiben Stream.Dispose() class namens Stream , um das ursprüngliche Verhalten unserer Bibliothek während der Konvertierung von DelegatingStream nach HttpWebRequest zu erhalten.

Ein Szenario, in dem Sie HttpClient sehen können, ist, wenn die Methode ObjectDisposedException eine getStream() -Instanz zwischenspeichert und sie für mehrere zukünftige Aufrufe zurückgibt. Wenn Stream über den Stream verfügt oder wenn Request.Dispose() über den Stream verfügt, erhalten Sie beim nächsten Versuch, den Stream zu verwenden, ein DoStuff2(Stream) .

    
Sam Harwell 05.12.2014, 22:43
quelle
7

Laut Sprachspezifikation , Abschnitt 3.9: "Wenn das Objekt, oder irgendein Teil davon, kann nicht durch eine mögliche Fortsetzung der Ausführung zugegriffen werden, außer der Ausführung von Destruktoren, das Objekt wird als nicht mehr verwendet betrachtet, und es wird für die Zerstörung in Frage kommen. " using führt einen Dispose Aufruf in einem finally Block (Abschnitt 8.13) ein, was etwas anderes als das Ausführen eines Destruktors ist und ausgeführt werden muss, außer wenn sogar finally Blöcke nicht ausgeführt werden (was die Spezifikation nicht tut) verdeckt nicht, und passiert normalerweise nur, wenn deine gesamte AppDomain sowieso auf dem Weg zum Friedhof ist.

Ein Objekt in einem using -Block ist nicht für GC geeignet. In Ihrem Beispiel ist request erst nach der using -Anweisung für eine Vernichtung geeignet, unabhängig davon, ob sie im Rest des Blocks verwendet wird. Der einzige Eckfall (wie in den Kommentaren anderer erwähnt) ist, wenn Ihre Dispose -Implementierung ihren Parameter this nicht verwendet. In diesem Fall würde Dispose nicht verhindern, dass das Objekt für die Vernichtung infrage kommt - aber wenn das passiert, ist die Frage, wann es gesammelt werden kann, müßig (wie es sein sollte - Garbage Collection sollte, wenn es korrekt implementiert wird, haben keinen beobachtbaren Effekt auf ein korrekt implementiertes Programm).

    
Jeroen Mostert 05.12.2014 15:56
quelle