2016-02-12 5 views
9

Ich verstehe, dass Enumeratoren und das Yield-Schlüsselwort verwendet werden können, um mit asynchronen/gestaffelten Operationen zu helfen, wie Sie MoveNext() aufrufen können, um den nächsten Codeblock auszuführen.Wie funktioniert GC mit IEnumerator und Ertrag?

Allerdings verstehe ich nicht wirklich, was dieses Enumerator-Objekt ist. Wohin geht der Speicher des Bereichs des Enumerators? Wenn Sie nicht einen Enumerator MoveNext() tun, wird es schließlich GC'd?

Grundsätzlich versuche ich, meine GC-Treffer niedrig zu halten, da ich möglicherweise eine Menge von Enumerators und GC kann ein Problem in Unity sein, vor allem aufgrund der älteren Version von Mono verwendet.

Ich habe versucht, dies zu profilieren, kann aber meinen Kopf immer noch nicht um sie wickeln. Ich verstehe das Scoping/Referenzieren, das mit Enumerators passiert, nicht. Ich verstehe auch nicht, wenn Enumeratoren als Objekte erstellt werden, wenn Sie eine aus einer Funktion erstellen, die ergibt.

Das folgende Beispiel zeigt meine Verwirrung besser:

// Example enumerator 
IEnumerator<bool> ExampleFunction() 
{ 
    SomeClass heavyObject = new SomeClass(); 
    while(heavyObject.Process()) 
    { 
     yield return true; 
    } 

    if(!heavyObject.Success) 
    { 
     yield return false; 
    } 

    // In this example, we'll never get here - what happens to the incomplete Enumerator 
    // When does heavyObject get GC'd? 
    heavyObject.DoSomeMoreStuff(); 
} 

// example call - Where does this enumerator come from? 
// Is something creating it with the new keyword in the background? 
IEnumerator<bool> enumerator = ExampleFunction(); 
while(enumerator.MoveNext()) 
{ 
    if(!enumerator.Current) 
    { 
     break; 
    } 
} 

// if enumerator is never used after this, does it get destroyed when the scope ends, or is it GC'd at a later date? 
+4

Der Compiler überschreibt Ihre Enumerator-Methode und verschiebt den Code in eine Klasse mit einem unsäglichen Namen. Diese Klasse implementiert eine Zustandsmaschine, bei der Ihre lokalen Variablen zu Feldern der Klasse werden, und ermöglicht, dass MoveNext() erneut eingegeben wird, ohne den Status zu verlieren. Die IEnumerator-Schnittstellenreferenz ist eigentlich eine Referenz auf ein Objekt dieser Klasse. Alles verschwindet, wenn diese Referenz erfasst wird. Sobald der Code die foreach-Schleife verlässt, ist er berechtigt, GC-ediert zu werden. –

+5

Beispiel von dem, was Hans sagte: http://goo.gl/fs4eNo – xanatos

+0

Okay, danke. Das tut nicht, was ich ALLES erwartete. Bedeutet das, Enumerators sind ziemlich schwer im Vergleich zu traditionellen Mitteln? Ich versuche, GC herunterzuhalten, aber es scheint, als würde ich immer neuen Müll erzeugen, wenn ich einen neuen Enumerator starte (und ich habe eine Menge Aufzählungen). – mGuv

Antwort

5

Sie sollten wahrscheinlich einen Enumerator Internal Post lesen. Von den Interna können Sie all diese Fragen beantworten.

Ein Crash-Kurs: Jede Ausführung der Iterator-Methode gibt ein neues Enumerator-Objekt zurück. Lokale Variablen werden zu Feldern.

Wenn niemand mehr dieses Enumerator-Objekt verwendet, kann es gesammelt werden. Außerdem werden alle Referenzen, die lokale Variablen erstellten, für GC-Zwecke entfernt.

Es gibt einige Randfälle, wenn genau lokale Variablen keine GC-Referenzen mehr sind. Wenn Ihre Enumeratoren ziemlich kurzlebig sind, spielt das keine große Rolle.

Die CLR weiß nicht, was ein Enumerator ist. Es ist nur eine Klasse, die vom C# -Compiler generiert wird.

Ziehen Sie den Link von xanatos in dieser Antwort, da es illustrativ ist und er scheint nicht eine Antwort zu schreiben: http://goo.gl/fs4eNo

+0

Richtig, das erklärt viel. Mir war nicht bewusst, wie viel Automagie es bei Enumerators machte. Ich habe viele Methoden, die Enumerators zurückgeben und ich habe viele Dinge, die sie aufrufen/verwenden. Das hört sich an, als könnte es ein GC-Problem sein, da jeder Enumerator, der ins Leben kommt, GCing benötigen wird. Es muss etwas sein, das ich profiliere. – mGuv

2

Der Enumerator ist die gleiche Art und Weise wie jede andere verwaltete Instanz verwaltet werden - wenn es außerhalb des Gültigkeitsbereiches ist. Wenn also MoveNext() nie aufgerufen wird, löscht der GC den Enumerator (oder markiert ihn besser zum Löschen), wenn er außerhalb seines Gültigkeitsbereichs liegt. Das Iterieren eines Enumerators findet nicht parallel statt, so dass sowohl die Ausführung als auch die Speicherbereinigung deterministisch und sequentiell ablaufen, wenn Sie damit fertig sind.

Wenn innerhalb einer Iterator-Methode die yield-return -Station nicht die allerletzte Anweisung überhaupt sein soll, bedeutet es einfach, dass, wenn MoveNext() aufgerufen wird, das aktuelle Ergebnis zurückgegeben werden soll. Aber alles hinter yield return läuft nach dem Anruf auf MoveNext so auch Ihr Anruf an DoSomeMoreStuff.

Ihre heavyObject hat jedoch den Umfang der Methode, so lebt es so lange wie der Iterator - wo der Iterator ist der Block in Ihrem while -loop. Das bedeutet, wenn Ihr Iterator nicht einmal eine Instanz zurückgibt, wird heavyObject sofort entsorgt, andernfalls wenn die Iteration beendet wurde.

3

// wenn enumerator nie danach verwendet wird, wird es zerstört werden, wenn der Umfang endet

Correct

Teilnahmeberechtigung für Sammel keine Referenzen wird aufweist. Für das Scoping von GC gibt es nichts Besonderes mit Enumeratoren. Enumeratoren werden auf die gleiche Weise entsorgt/gelöscht, wenn sie wie jeder andere Typ außerhalb des Gültigkeitsbereichs liegen.

+0

Wenn ein Enumerator nie irgendwo, sondern lokal innerhalb einer Funktion referenziert wird, wird er billig/sofort gecodiert? Ich verstehe GC nicht ganz, aber ich weiß, dass es nirgendwo innerhalb der Funktion referenziert werden kann, es ist ziemlich einfach zu erkennen, dass es aufgeräumt werden kann. – mGuv

+0

Ja, das ist richtig. – CharithJ