2016-02-12 8 views
9

Rozumiem, że moduły wyliczające i słowo kluczowe yield mogą być użyte do pomocy w asynchronicznych/przestawionych operacjach, ponieważ można wywołać MoveNext(), aby uruchomić następny blok kodu.Jak działa GC z IEnumeratorem i wydajnością?

Jednak tak naprawdę nie rozumiem, co to jest obiekt Enumerator. Skąd bierze się pamięć w użyciu zakresu modułu wyliczającego? Jeśli nie wykonasz w pełni Enumeratora, czy ostatecznie otrzymasz GC?

Zasadniczo staram się nie dopuścić do spadku wartości GC, ponieważ potencjalnie używam wielu enumeratorów, a GC może być problemem w Unity, szczególnie ze względu na starszą wersję Mono, której używa.

Próbowałem profilować to, ale nie mogę jeszcze owinąć ich głową. Nie rozumiem zakresu/odniesienia, które ma miejsce w przypadku enumeratorów. Nie rozumiem też, czy enumeratory są tworzone jako obiekty, gdy tworzy się je z funkcji, która daje.

Poniższy przykład pokazuje mój zamieszanie lepiej:

// 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

Kompilator przepisuje metodę modułu wyliczającego i przenosi kod do klasy o niewypowiedzianej nazwie. Ta klasa implementuje automat stanów, w którym zmienne lokalne stają się polami klasy, pozwala na ponowne wprowadzanie metody MoveNext() bez utraty stanu. Odwołanie do interfejsu IEnumerator jest w rzeczywistości odniesieniem do obiektu tej klasy. Wszystko mruga w nieskończoność, gdy odwołanie jest zbierane, kwalifikuje się do GC-ed, gdy tylko twój kod opuści pętlę foreach. –

+5

Przykład tego, co powiedział Hans: http://goo.gl/fs4eNo – xanatos

+0

OK, dziękuję. To nie robi tego, czego się spodziewałem. Czy to oznacza, że ​​Enumeratory są dość ciężkie w porównaniu do tradycyjnych metod? Próbuję utrzymać GC w dół, ale wydaje się, że zamierzam utworzyć nowe śmieci za każdym razem, gdy uruchamiam nowy moduł wyliczający (i mam wiele modułów wyliczających). – mGuv

Odpowiedz

5

Prawdopodobnie powinieneś przeczytać wpis dotyczący modułów wewnętrznych modułu wyliczającego. Od internals można odpowiedzieć na wszystkie te pytania.

Krytyczny kurs: Każde wykonanie metody iteratora zwraca nowy obiekt modułu wyliczającego. Zmienne lokalne stają się polami.

Jeśli nikt nie używa już tego modułu wyliczającego, jest on uprawniony do odbioru. Poza tym wszystkie odniesienia, które tworzyły zmienne lokalne, znikają w celach GC.

Istnieje kilka przypadków krawędziowych, gdy dokładnie zmienne lokalne przestają być referencjami GC. Jeśli twoi rachmistrze są dość krótko żyjący, nie ma to większego znaczenia.

CLR nie wie, co to jest moduł wyliczający. To tylko klasa generowana przez kompilator C#.

Ciągnięcie Link przez Xanatos do tej odpowiedzi, ponieważ jest ilustracyjny i nie wydaje się, aby opublikować odpowiedź: http://goo.gl/fs4eNo

+0

Dobrze, to bardzo wyjaśnia. Nie wiedziałem, o ile automagicznie to robi, jeśli chodzi o Enumeratory. Mam wiele metod, które zwracają Enumeratory i mam wiele rzeczy dzwoniących/używających ich. Wygląda na to, że może to być problem z GC, ponieważ każdy Enumerator, który nadejdzie, będzie potrzebował GCing. To musi być coś, co profiluję. – mGuv

2

moduł wyliczający jest zarządzany w taki sam sposób jak każdy inny zarządzany przykład - gdy jest poza zakresem. Zatem jeśli MoveNext() nigdy nie zostanie wywołany, GC kasuje (lub lepiej zaznacza to do usunięcia) moduł wyliczający, gdy jest poza jego zakresem. Iteracja modułu wyliczającego nie odbywa się w żadnej równoległej, więc zarówno wykonanie, jak i garbage-collection przebiegają deterministycznie i sekwencyjnie, gdy skończysz.

Gdy wewnątrz metody iteratora, nie jest to wcale ostatnia instrukcja, oznacza to, że po wywołaniu MoveNext() należy zwrócić bieżący wynik. Jednak wszystko za numerem yield return jest uruchamiane po połączeniu z numerem MoveNext, a więc także pod numerem DoSomeMoreStuff.

Twój adres heavyObject ma jednak zakres tej metody, dlatego też działa tak długo, jak iterator - gdzie iterator jest blokiem wewnątrz twojego. Oznacza to, że jeśli twój iterator nie zwróci żadnej instancji, to natychmiast zostanie usunięta jakakolwiek instancja, w przeciwnym razie po zakończeniu iteracji.

3

// jeśli wyliczający nigdy nie jest stosowany po to, ma ona ulec zniszczeniu podczas zakres kończy

Prawidłowe

Uprawnienie do gromadzenia się nie mając odniesienia. W przypadku zakresu GC nie ma nic szczególnego w modułach wyliczających. Enumeratory są usuwane/kasowane w taki sam sposób, gdy wychodzą poza zakres, jak każdy inny typ.

+0

Jeśli Enumerator nigdy nie był nigdzie polecany, ale lokalnie z poziomu funkcji, czy GC byłby tanio/natychmiastowy? Nie w pełni rozumiem GC, ale wiem, że skoro nie może to być nigdzie indziej, ale w ramach funkcji, to dość proste jest wykrycie, że można je wyczyścić? – mGuv

+0

Tak, to jest poprawne. – CharithJ