in C#, Programming, Tutorial, Unity

Nested Coroutines in Unity

This tutorial shows how to make the most out of coroutines in Unity.

Introduction

Each Unity script comes with two important functions: Start and Update. While the former is invoked when an object is enabled after being created, the latter is called during each frame. By design, the next frame cannot start until Update has terminated its job. This introduces a strong design limitation: Update cannot easily model events that last for more than one frame.

To be completely honest, every custom behaviour you can imagine can be implemented using Start and Update. However, events that happens over multiple frames (such as animations, dialogues, waits, …) are harder to code. This is because their logic cannot be written in a consistent flow. It has to be fragmented, spread over multiple frames. This often leads to code that is not just harder to write, but also harder to maintain.

What would be perfect is to have something that can be executed in parallel, unconstrained from the short life of a single frame. If you are a programmer, this will probably resonate with the concept of thread. Threads are pieces of code that are executed in parallel. Working with threads, however, is very tricky. This is because when multiple threads are working on a shared variable without any limitation , there can be issues. By design, Unity strongly discourages the use a threads. However, it offers a good compromise: coroutines. Coroutines are functions which can lasts more than one frame. Moreover, they come with expressive constructs to interrupt and resume their executions due to arbitrary conditions.

Coroutines are normal C# functions which return IEnumerator. To execute such a function like a coroutine (and not like a traditional function), one has to use the StartCoroutine method (UnityDoc). For instance:

void Start ()
{
    // Execute A as a coroutine
    StartCoroutine( A() );
}

IEnumerator A ()
{
    ...
}

executes A as a coroutine. The method StartCoroutine terminates immediately, but spawns a new coroutine that is executed in parallel.

❓ Why coroutines must have IEnumerator has their return type?
Coroutines in Unity are based on C# iterators, which have been covered in the first part of this tutorial: Iterators in C#: yield, IEnumerable and IEnumerator. Iterators are an expressive way to model objects that can be iterated upon, such as lists and other collections. In C#, this is done using the interface IEnumerator.

Unity treats coroutines as “lists of code”. Asking for a coroutine to produce its next element has the meaning of executing its next step. As such, each function that needs to last more than one frame is required to use IEnumerator as its return type.

 

❓ Are coroutines executed in parallel?
No. Unity is generally not thread safe. Which means that running two pieces of code in parallel can potentially break your game. As such, during each frame, Unity executes a bit of each active coroutine, sequentially.

 

❓ Can I still have threads in Unity?
Yes. Several parts of Unity run in separate threads (audio, Mecanim, Skinning, …). While GameObjects and MonoBehaviours are not designed to be thread safe, this does not mean they cannot be accessed by threads. Each access to shared resource (like a game object or a variable) needs to be properly controlled, to avoid inconsistent results.  Threads can indeed be used, but for the vast majority of your everyday applications the cost of micromanaging accesses to shared resources is simply not worth it.

Synchronous Waits

If you have used coroutines before, it is likely that you have already encountered the class WaitForSeconds (UnityDoc). Like all the other classes that extend YieldInstruction, it allows to temporarily suspend the execution of a coroutine. When coupled with yield return, WaitForSeconds provides an expressive way to delay the execution of the remaining code.

The following piece of code shows how it can be used within a coroutine:

IEnumerator A()
{
    ...

    yield return new WaitForSeconds(10f);

    ...
}

The diagram above, loosely inspired by the sequence diagrams in UML (Wikipedia), illustrates the effect of WaitForSeconds. When invoked in a coroutine (called A) it suspends its execution until a certain amount of time has passed. This type of wait is called synchronous, because the coroutine waits for for another operation to complete.

❓ What does yield mean?
The keyword yield is used by C# to write iterators. Objects that return IEnumerator can be treated as “collections” and can be iterated using a foreach loop. In this context, yield is the way to return an object in the for loop. This is discussed in great detail in Iterators in C#: yield, IEnumerable and IEnumerator.

 

❓ Hey! This is not how a Sequence Diagram works in UML!
I know. And that is not a question. 💁

Asynchronous Coroutines

Unity also allowed to start new coroutines within an existing coroutine. The most simple way in which this can be achieved, is by using StartCoroutine. When invoked like this, the spawned coroutine co-exist in parallel with the original one. They do not interact directly, and most importantly they do not wait for each other. In comparison with the synchronous wait presented in the previous paragraph, this situation is asynchronous, at the two coroutines do not attempt to remain in synch.

IEnumerator A()
{
    ...

    // Starts B as a coroutine, and continue the execution
    StartCoroutine( B() );

    ...
}

It is important to notice that, in this example, B is a totally independent coroutine. Terminating A does not affect B, and vice versa.

Synchronous Coroutines

It is also possible to execute a nested coroutine and to wait for its execution to be completed. The simplest way to do this, is by using yield return.

IEnumerator A()
{
    ...

    // Waits for B to terminate
    yield return StartCoroutine( B() );

    ...
}

It’s worth noticing that, since the execution of A is suspended during the execution of B, this particular case does not need to start another coroutine. One might be tempted to optimise the coroutine by writing something like this:

IEnumerator A()
{
    ...

    // Executes B as part of A
    B();

    ...
}

Executing B as a traditional function has almost the same effect. The only difference, however, is that B will be executed in a single frame. By using StartCoroutine, instead, A is suspended and the next frame can occur.

The reason why this example is shown, however, is to introduce more complex cases of coroutine synchronisation.

Parallel Coroutines

When a coroutine is started using StartCoroutine, a special object is returned. This can be used to query the state of the coroutine and, optionally, to wait for its termination.

In the example below the coroutine B is executed asynchronously. Its father A can continue its execution for as long as it needs. Then, if necessary, it can yield to the reference to B for a synchronous wait.

IEnumerator A()
{
    ...
    
    // Starts B as a coroutine and continues the execution
    Coroutine b = StartCoroutine( B() );
    
    ...
    
    // Waits for B to terminate
    yield return b;
    
    ...
}

This is particularly helpful if you want to start several parallel coroutines, all at the same time:

IEnumerator A()
{
    ...
    
    // Starts B, C, and D as coroutines and continues the execution
    Coroutine b = StartCoroutine( B() );
    Coroutine c = StartCoroutine( C() );
    Coroutine d = StartCoroutine( D() );
    
    ...
    
    // Waits for B, C and D to terminate
    yield return b;
    yield return c;
    yield return d;
    
    ...
}

This new paradigm allows to start an arbitrary numbers of parallel computations, and to resume the execution when all of them have terminated.

Conclusion

This post shows several different patterns that can be implemented in your game to use coroutines effectively. The next posts of this series will focus on how to extends coroutines to support custom waits and events.

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

26 Comments

  1. In the case of your “synchronous coroutines”, your two examples are not strictly identical. In the first case, control will be yielded when B ends. In the second case it will not.

  2. Tip: You can also declare the Start function as returning IEnumerator

    It might also be good to mention that the lifetime of the Coroutine is bound to the GameObject / Monobehaviour that started it.

  3. Seriously man it is a great article…I never properly understood the coroutines before I read the article..It really made my day and is very informative

  4. It’s a good article and explanation of Coroutines, but I’d warn the readers that there’s no true parallelism when using them, since they always run concurrently inside the main Unity thread.

    • Hey!
      Yes, that is going to be the next tutorial!
      I’ve decided to show how to use them FIRST, and to explain how they ACTUALLY work after. It might not be the best way to do it, but this way the readers who need to learn coroutines can do it without getting too scared about threads stuff! 🙂

  5. Nice article!

    You wrote in the introduction that Unity strongly discourages the use of threads. What’s your source of information for this statement? I’m asking because I’m finding it hard to believe.

    Joachim Ante and his team has been working on the job system, which is multi threaded. The transform hierarchy is becoming thread safe, in order for you to access Transform objects in threads. With Unity’s focus on increasing performance, and the fact that most CPUs today have more than one core, it makes no sense to me that Unity discourages the use of threads. My impression is that it’s quite the opposite.

  6. I have been very confused by this topic due to the following. It seems that
    IEnumerator A()
    // Waits for B to terminate
    yield return StartCoroutine( B() );
    }

    IEnumerator A()
    // Waits for B to terminate
    yield return B();
    }

    work just the same. I don’t know if it happened on an update but it surelky diddn’t seem to work in the past: https://answers.unity.com/questions/14081/nested-coroutines.html

    Somehow unity is flattening the IEnumerators and the StartCoroutine call is implicit when you yield an IEnumerator from your Coroutine

    • I have also encountered this and would like to know if it is necessary to use the StartCoroutine or maybe just calling a yield return on a function that its return value is IEnumerator is enough.

  7. The example of calling B() above within a coroutine is simply not true – as of Unity 5.4+ (maybe earlier), Unity can and will terminate the B code without executing it. There are edge cases in whcih unity will actually run the code, but I’m not sure what the pattern is.

  8. As cool as it seems, you’re in for troubles if later down the road you ever wish to stop some coroutines prematurely. You can’t (or with overkill dirty hacks & workarounds).

    I experienced it the hard way sadly. First, if you stop a coroutine, it doesn’t stop its nested coroutines.

    Once I managed to stop them too, I realized that all parent coroutines waiting on their subcoroutines to finish do hang forever as the nested coroutine didn’t stop on its own. Fun.

  9. This article clarified a lot, and really improved my insight.
    Nested coroutines make so much cleaner code.
    I recommenced it to several other Unity programmers.
    Thank a lot!

  10. I have a question on synchronous coroutines as shown in your example: what is the proper yield statement in coroutine B in this case? I would like to put several coroutines in series like this, such that they always run in order.

    Thanks!

      • I would like to execute a number of coroutines in order repeatedly, such that they repeatedly execute in the same order.

        • This can be done running this coroutine:

          IEnumerator MainCoroutine ()
          {
          while (Running)
          {
          yield return StartCoroutine(A()); // Waits for A
          yield return StartCoroutine(B()); // Waits for B
          yield return StartCoroutine(C()); // Waits for C
          } // Repeats
          }

          • ok, that’s helpful, but what is the yield statement inside A, B and C in that case.

            Thanks!

Webmentions

  • Slippy Maps with Unity - Alan Zucconi August 18, 2022

    […] If you are unfamiliar with how coroutines in Unity work, and how one can wait for another, I suggest reading one of the most popular articles on this blog: Nested Coroutines in Unity. […]

  • Unity Tips and Tricks: Nested Coroutines - August 18, 2022

    […] Nested Coroutines in Unity Author chrisPosted on April 1, 2020April 1, 2020Categories Uncategorized […]

  • Nested Coroutines in C# with Unity | Ace Infoway August 18, 2022

    […] Coroutines in C# with Unity {$excerpt:n} submitted by /u/AlanZucconi [link] [comments] Source: […]