简体   繁体   中英

How does GC work with IEnumerator and yield?

I understand that enumerators and the yield keyword can be used to help with async/staggered operations, as you can call MoveNext() to run the next block of code.

However, I don't really understand what that Enumerator object is. Where does the memory in use of the scope of the Enumerator go? If you don't MoveNext() an Enumerator all the way, does it get GC'd eventually?

Basically, I'm trying to keep my GC hits down, as I am potentially using a LOT of Enumerators and GC can be an issue inside Unity, especially due to the older version of Mono it uses.

I have tried to profile this but can't wrap my head around them still. I don't understand the scoping/referencing that happens with Enumerators. I also don't understand if Enumerators are created as Objects when you create one from a function that yields.

The following example shows my confusion better:

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

You probably should read an enumerator internals post. From the internals you can answer all these questions.

A crash course: Each iterator method execution returns a new enumerator object. Local variables become fields.

If nobody uses that enumerator object anymore it is eligible for collection. Also, all references that local variables were creating go away for GC purposes.

There are some edge cases for when exactly local variables stop being GC references. If your enumerators are fairly short lived this does not matter much.

The CLR does not know what an enumerator is. It's just a class generated by the C# compiler.

Pulling the link by xanatos into this answer since it's illustrative and he does not seem to post an answer: http://goo.gl/fs4eNo

// if enumerator is never used after this, does it get destroyed when the scope ends

Correct

Eligibility for collecting is having no references. For GC's scoping there is nothing special with enumerators. Enumerators disposed/cleared in the same way when they go out of scope as any other type.

The enumerator is managed the same way as every other managed instance - when it is out of scope. Thus if MoveNext() is never called the GC deletes (or better marks it for deletion) the enumerator when it is out of its scope. Iterating an enumerator does not happen in any parallel so both execution and garbage-collection run deterministically and sequentually when you´re done with it.

When inside an iterator-method the yield-return -stament is not ment to be the very last statement at all, it simply means that when MoveNext() is called the current result should be returned. However everything behind yield return runs after the call to MoveNext so also your call to DoSomeMoreStuff .

Your heavyObject however has the scope of the method, thus it lives as long as the iterator - where the iterator is the block inside your while -loop. This means that if your iterator does not even return any instance heavyObject will be immediately disposed, otherwise when iteration has been finished.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM