x


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

more ▼

asked Mar 30, 2010 at 08:10 AM

KvanteTore gravatar image

KvanteTore
1.8k 21 26 48

I made a suggestion to help implement nested coroutines in Unity: http://feedback.unity3d.com/forums/15792-unity/suggestions/636531-make-yieldinstruction-extendable

Apr 12, 2010 at 12:58 PM KvanteTore

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. ;-)

Jan 04, 2013 at 03:53 PM Cawas

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.

Jan 04, 2013 at 06:59 PM KvanteTore

@Cawas, it turns out that IEnumerator.MoveNext should be called before IEnumerator.Current is accessed. I've updated the question to reflect this.

Jan 07, 2013 at 03:18 PM KvanteTore

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 MoveNext being first, unlike the proper link: http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx so, nice finding you did there! :-)

Jan 07, 2013 at 03:53 PM Cawas
show all comments (comments are locked)
10|3000 characters needed characters left

5 answers: sort voted first

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.

more ▼

answered Apr 28, 2010 at 01:59 AM

YardGnome gravatar image

YardGnome
376 1 1 7

This is correct answer.

yield return StartCoroutine( Func1() );

Apr 28, 2010 at 02:07 AM evelynliu

Sweet! Thank you very much :)

Apr 28, 2010 at 05:11 AM KvanteTore

That's beautiful. What a great feature of the unity framework!

Feb 16, 2011 at 09:45 AM Ouchie

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.

Oct 05, 2011 at 12:33 PM KvanteTore

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.

Jan 04, 2013 at 01:51 PM Cawas
(comments are locked)
10|3000 characters needed characters left

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
more ▼

answered Mar 30, 2010 at 09:16 AM

duck gravatar image

duck ♦♦
46.6k 126 187 452

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 :)

Mar 30, 2010 at 09:38 AM KvanteTore

Only, you yield null from the Func2 instead of passing on the yielded value from Func1. Is there a reason for that?

Mar 30, 2010 at 09:39 AM KvanteTore

Oh, sorry I completely overlooked your second snippet! Um, no reason really - The code in Func1 is executed by calling MoveNext() 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.

Mar 30, 2010 at 09:45 AM duck ♦♦

Actually, you should use while (f1e.MoveNext()) { yield return f1e.Current; }. This allows to yield a new WaitForSeconds() from Func1, which is totally awesome.

Jan 06, 2012 at 09:33 PM Calvin1602

@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 ;)

Jan 07, 2012 at 12:42 AM Bunny83
(comments are locked)
10|3000 characters needed characters left

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();.

more ▼

answered Mar 30, 2010 at 08:23 AM

qJake gravatar image

qJake
12.6k 91 121 200

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.

Mar 30, 2010 at 08:30 AM KvanteTore

And I don't want to block the entire Unity thread, I just want to halt the execution of Func2, coroutine-style.

Mar 30, 2010 at 08:31 AM KvanteTore

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 instead 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.

Mar 30, 2010 at 04:30 PM qJake

It appears most Unity specific methods are not thread-safe though. You can't, for instance, create an object that derives from a MonoBehaviour 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-MonoBehaviour 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.

Nov 30, 2010 at 09:47 PM Antitheory
(comments are locked)
10|3000 characters needed characters left

A way that is a bit cleaner is:

IEnumerator Func2()
{
    //wait for the completion of Func1
    yield return StartCoroutine(Func1());
}
more ▼

answered May 26, 2011 at 08:03 PM

luizgpa gravatar image

luizgpa
1k 1 5 10

(comments are locked)
10|3000 characters needed characters left

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);
    }
more ▼

answered Feb 13, 2013 at 12:21 PM

Freezy gravatar image

Freezy
0 1 2 3

(comments are locked)
10|3000 characters needed characters left
Your answer
toggle preview:

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Topics:

x8522
x576

asked: Mar 30, 2010 at 08:10 AM

Seen: 19980 times

Last Updated: Apr 14 at 10:52 PM