in c#, programming, tutorial, Unity3D

Nested Coroutines in Unity

Share Button

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:

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:

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.

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.

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:

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.

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

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! ♥

For the past three years I've been dedicating more and more of my time to the creation of quality tutorials, mainly about game development and machine learning. If you think these posts have either helped or inspired you, please consider supporting this blog.

Paypal
Twitter_logo

Don't miss the next tutorial!

There's a new post every Wednesday: leave your email to be notified!


Write a Comment

Comment

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

Webmentions

  • Nested Coroutines in C# with Unity | Ace Infoway September 7, 2017

    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.