- Home /
yield and coroutines, how do they actually work?
Hello,
I think I have a good understanding of the yield keyword, still I do not understand how practically something like :
IEnumerator Example() {
print(Time.time);
yield return new WaitForSeconds(5);
print(Time.time); }
can work.
I am sure that the MonoBehaviour StartCoroutine registers the single routines, which are then called every frame. In this way the Iterator Block is recalled as it should be. It is everything clear, expect the fact I do not understand how the iterator block can know that the YieldInstruction is actually finished. I mean I was expecting something like yield break, but things like :
yield return www o yield return WaitForSecond() look like they should never end OR actually called just once.
Please help me to understand what is the trick Unity does.
N.B. I was expecting at least to see the WWW class or the YieldInstruction to be IEnumerable, but it is not even this the case :(
Answer by Statement · Mar 28, 2012 at 04:17 PM
Unity just make use of enumerators. The compiler will add some extra code to the function so the code can jump to where it yielded next time it is enumerated. This allow us to execute parts of the code in small steps.
You can do your own code that goes through an enumerator in the same way.
IEnumerator e = Example();
while (e.MoveNext())
{
Debug.Log("Yielded: " + e.Current);
}
When yielding a WaitForSeconds, Unity will wait with calling MoveNext until the duration has passed. If the object yielded is null, it will wait until next frame. If it is a WaitForSeconds, it will read the duration. "Simple" as that.
To get an idea about this, you could think:
if (e.Current is WWW) ... // call e.MoveNext when the download finished!
You can make your own system like this by keeping a list of pairs of IEnumerator and Func predicates (for letting know if it is ok to advance to the next yield).
Each update, you loop through all pairs, check if the predicate allow moving the enumerator. If it does, move the enumerator and set the predicate to the next criteria. For example, if e.Current was WWW, you might make a predicate to check if WWW has completed or not. If it is WaitForSeconds, you make a predicate that return true once the time reaches Now + Duration.
I hope you understand more about how this works. IEnumerator is nothing specific with Unity, so you can read more about IEnumerator online to go more in depth.
let's make a better example:
IEnumerator WaitForSecondsBlockIterator() { yield return WaitForSeconds(2); }
IEnumerator e = WaitForSecondsBlockIterator();
while (e.$$anonymous$$oveNext()) { Debug.Log("Yielded: " + e.Current); }
if I this is corret, I have new questions:
how many times this line is actually called:
yield return WaitForSeconds(2);
once or every frame until the 2 seconds are passed?
and I do not understand when exactly $$anonymous$$oveNext starts to return false and why
Once. It is yielded (returned, then continue execution there next enumeration), and the code will not be called until after 2 seconds. $$anonymous$$oveNext return false when you reach the end of the method or you yield break;
I wonder now how the system could work if an Enumerable yield another Enumerable function (which could be async in its turn)
Unity will not pick this up. You need to yield return StartCoroutine("OtherCoRoutine") for it to pause execution. If you want to support nested enumerators, then you have to write your own code around it. It shouldn't be too hard if you follow the advice I gave in my post. if (e.Current is IEnumerator) ...
I usually hate to link dump, but this provides a much better, thorough explaination of how coroutines work http://www.altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/
Answer by Douvantzis · Jun 15, 2016 at 01:37 PM
As @Statement explained, Unity makes use of enumerators. However, I'd like to go a bit deeper on how I think it works internally.
When your co-routine returns an object by calling yield return new SomeType()
, Unity will check its type. If it is of type YieldInstruction or Ienumerator, Unity will call MoveNext() on that enumerator as well. As long as MoveNext()
returns true
, Unity will continue iterating over this enumerator in the next frames and it will not continue your initial coroutine execution.
From within your returned IEnumerator you can yield return
another IEnumerator. This way you can chain multiple Enumerators. Inception! I know!
If you wonder how WaitForSeconds and other extensions of YieldInstruction work internally, check out this documentation page. It seems that YieldInstrction could implement the Ienumerator interface like this:
class YieldInstruction: IEnumerator {
// default implementation that must be overridden
public bool keepWaiting { get { return false; } }
public object Current { get { return null; } }
public bool MoveNext() { return keepWaiting(); }
public void Reset() {}
}
Just found the CustomYieldInstruction too, after discovering WaitWhile and WaitUntil at the same time via autocompletion. They seem convenient to wait for scene loading and so on.