- Home /
Nested coroutines
I have one routine, Func1
which returns an IEnumerator
. This method is called by another coroutine Func2
that has been started with StartCoroutine
. The intent is that Func2
should wait for the completion of Func1
.
Ideally, I'd just yield the IEnumerator returned by Func1
and let the
IEnumerator Func1() { for (int i=0; i<10; i++) { yield return null; } yield break; }
IEnumerator Func2() { //wait for the completion of Func1 yield return Func1(); }
However, Unity doesn't seem to recursively iterate over IEnumerators, so Func2 yields just once. An alternative is to manually iterate over the IEnumerator,
IEnumerator Func2()
{
//wait for the completion of Func1
IEnumerator e = Func1();
while (e.MoveNext()) yield return e.Current;
}
but I was looking for a less clunky solution, much like the way I can yield a WWW object and wait for its completion?
EDIT: Removed superfluous yield statement in func2. It turns out, that MoveNext should be called before accessing IEnumerator.Current
I made a suggestion to help implement nested coroutines in Unity: http://feedback.unity3d.com/forums/15792-unity/suggestions/636531-make-yieldinstruction-extendable
I believe there's no need for the whole line yield return e.Current;
or at least on my experiments I removed it and nothing wrong happened. Since this is actually a better solution than using StartCoroutine
, because of static methods for one, I think it's quite relevant to make it shorter. ;-)
you'll need the yield return e.Current line in order to actually give control back to the unity engine. without it the innermost coroutine will be run as if there were no yield statements at all.
@Cawas, it turns out that IEnumerator.$$anonymous$$oveNext should be called before IEnumerator.Current is accessed. I've updated the question to reflect this.
Now, reading it as it is, without knowing the line was removed, that "edit" comment makes little sense... And your link is to IEnumerable
, which is another thing and doesn't say anything about $$anonymous$$oveNext
being first, unlike the proper link: http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx so, nice finding you did there! :-)
Answer by YardGnome · Apr 28, 2010 at 01:59 AM
You want:
IEnumerator Func2()
{
yield return StartCoroutine( Func1() );
}
Func2
will not continue execution until the Func1
coroutine has completed. StartCoroutine()
returns a Coroutine
object, and this object can be returned by yield
. Unity knows to finish executing the new Coroutine
object before resuming the original function. You can use this technique to chain and nest coroutines.
For example: You want to create an object that does three in this order: fade in, move, and then shoot a gun. But since moving and shooting is very common in this game, you want to have a single function that simple does them both. You could write something like:
public void Start() { // Somebody starts the coroutine chain by calling: StartCoroutine( FadeAndMoveAndShoot() ); }
private IEnumerator FadeAndMoveAndShoot() { print( "FadeAndMove Start: " + Time.time ); yield return StartCoroutine( Fade() ); print( "FadeAndMove Middle: " + Time.time ); yield return StartCoroutine( MoveAndShoot() ); print( "FadeAndMove End: " + Time.time ); }
private IEnumerator Fade() { print( "Fade Start: " + Time.time ); yield return new WaitForSeconds( 2.0f ); print( "Fade End: " + Time.time ); }
private IEnumerator MoveAndShoot() { print( "Move Start: " + Time.time ); yield return new WaitForSeconds( 3.0f ); print( "Move Middle: " + Time.time ); yield return StartCoroutine( Shoot() ); print( "Move End: " + Time.time ); }
private IEnumerator Shoot() { print( "Shoot Start: " + Time.time ); yield return new WaitForSeconds( 1.0f ); print( "Shoot End: " + Time.time ); }
This will essentially output:
FadeAndMove Start: 0
Fade Start: 0
Fade End: 2
FadeAndMove Middle 2
Move Start: 2
Move Middle: 5
Shoot Start: 5
Shoot End: 6
Move End: 6
FadeAndMove End: 6
The numbers are in seconds, and you can see that the original FadeAndMove courtine takes 5 seconds to complete. The desired behavior.
This is correct answer.
yield return StartCoroutine( Func1() );
That's beautiful. What a great feature of the unity framework!
I have since learned about some snags in this approach. If one of the coroutines happens to throw an exception, the execution of all the nested coroutines would stop abruptly. I've therefore fallen back on the approach of manually iterating through the nested IEnumerators.
This doesn't work on static
methods! You'd need an instance, in which case it's probably better to implement Tore's manual iteration.
Answer by duck · Mar 30, 2010 at 09:16 AM
You can do this by "manually" iterating through the 2nd coroutine's IEnumerator object that it returns, like this:
IEnumerator Func2() { Debug.Log("Func2 start");
// manually iterate over Func1
IEnumerator f1e = Func1();
while (f1e.MoveNext()) {
yield return null;
}
Debug.Log("Func2 complete");
}
IEnumerator Func1() { Debug.Log("Func1 start"); for (int i = 0; i < 10; i++) { Debug.Log("Func1: " + i); yield return null; } Debug.Log("Func1 complete"); yield break; }
Output:
Func2 start
Func1 start
Func1: 0
Func1: 1
Func1: 2
Func1: 3
Func1: 4
Func1: 5
Func1: 6
Func1: 7
Func1: 8
Func1: 9
Func1 complete
Func2 complete
yes, that is the same solution as I posted in my second code snippet, but it's a bit clunky. I was hoping I had overlooked something more elegant :)
Only, you yield null from the Func2 ins$$anonymous$$d of passing on the yielded value from Func1. Is there a reason for that?
Oh, sorry I completely overlooked your second snippet! Um, no reason really - The code in Func1 is executed by calling $$anonymous$$oveNext() so I'm not what difference (if any) it makes to yield null rather than the return value of Func1. I have a feeling that it only affects how long Unity waits before calling the coroutine again.
Actually, you should use while (f1e.$$anonymous$$oveNext()) { yield return f1e.Current; }
. This allows to yield a new WaitForSeconds()
from Func1, which is totally awesome.
@Calvin1602: absolutely ;) I've just read the question and the answers. After reading this one i just wanted to add a comment like yours, but i've found your comment before i start writing ;)
Answer by fabiopicchi · May 27, 2018 at 12:34 PM
I am very confused about this because
IEnumerator Func1()
{
for (int i=0; i<10; i++)
{
yield return null;
}
}
IEnumerator Func2()
{
//wait for the completion of Func1
yield return Func1();
}
Seems to be working just fine. Did Unity do something smart to detect IEnumerators being returned?
This has been changed recently. I don't remember which Untiy version actually introduced this "feature". I think it was about 5.6 but I'm not sure about that. Now if you yield an IEnumerator instance Unity will automatically start a new coroutine for the ienumerator and yield on that ins$$anonymous$$d
Answer by qJake · Mar 30, 2010 at 08:23 AM
You may be over-thinking this...
yield return Func1();
You want this statement to "hold" until Func1(
) is completed, right? You don't need an IEnumerator to do that... standard, regular function calls (void Whatever();
) block the current thread until they've completed.
So if you want to call Func1()
and have it stop the current thread until it's complete, just... call it! Your first set of code, modified, would look like this:
void Func1() { for (int i = 0; i < 10; i++) { return; // return something here } return; // return something else here }
IEnumerator Func2() { //wait for the completion of Func1 Func1(); }
And then start a new coroutine with Func2();
.
Ah. That would work if Func1
is performs a simple computational task, but my Func1
is not quite as simple as this. It is waiting for external resources, which typically will not be available within one frame.
And I don't want to block the entire Unity thread, I just want to halt the execution of Func2, coroutine-style.
Inside of a coroutine, as far as I understand it, you wouldn't block the entire Unity thread, just that one coroutine.
You could also just use standard C# threading ins$$anonymous$$d of mucking around in coroutines. Start a new thread that loads your external resource, and then call an event when it's completed. It's essentially an asynchronous action, and there is tons of example code and documentation online regarding threads and events with respect to C#. If you want me to, though, I can write a small set of example code for you. Just let me know.
It appears most Unity specific methods are not thread-safe though. You can't, for instance, create an object that derives from a $$anonymous$$onoBehaviour within a new and separate thread. If you call an event asynchronously (with BeginInvoke) it happens in a new thread and cant access Unity's API. I am using a separate thread to do background loading for some non-$$anonymous$$onoBehaviour objects and then polling for completion from a Unity Coroutine. Seems to be working so far. The downside is you cant actually create GameObjects in the background apparently.
Answer by Freezy · Feb 13, 2013 at 12:21 PM
You can also pass the IEnumerator from another function
This could allow all sorts of extensions, like a workaround for default parameters or classes or functions which cannot for whatever reason be called with a coroutine.
[ContextMenu("Test")]
void CallStuff()
{
StartCoroutine(Func2());
}
IEnumerator Func1(int a)
{
Debug.Log("Func1 wait for " + a);
yield return new WaitForSeconds(a);
Debug.Log("Func1 done");
}
IEnumerator Func2()
{
Debug.Log("Func2");
return Func1(2);
}
Your answer
Follow this Question
Related Questions
The name 'Joystick' does not denote a valid type ('not found') 2 Answers
Trouble with Iterative Coroutine 1 Answer
While loop yield lag? 1 Answer
NullReferenceException in StartCoroutine 1 Answer
Returning an IEnumerator as an int? 1 Answer