This tutorial shows how to make the most out of coroutines in Unity.
- Introduction
- Part 1. Synchronous Waits
- Part 2. Asynchronous Coroutines
- Part 3. Synchronous Coroutines
- Part 4. Parallel Coroutines
- Conclusion
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.
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?
❓ Can I still have threads in Unity?
GameObject
s and MonoBehaviour
s 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.
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!
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.
- Part 1. Iterators in C#: yields, IEnumerable and IEnumerator
- Part 2. Nested Coroutines
- Part 3. Extending Coroutines
💖 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.
📧 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. ❤️🧔🏻
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.
what a cliffhanger at the end
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.
Thank you! 😀
I’ll write more about coroutines in the next weeks! 😀
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
Thank you hehe! 🙂
Looking forward for more of your posts on Unity..
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! 🙂
Is part 3 available part available? Extending Coroutines?
Not yet unfortunately!
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.
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.
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.
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.
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!
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’m not sure I understand the question.
Can you make an example of what you want to achieve?
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!
the demonstration is so great dude, but where can I find the Part.3?
Is there gonna be part 2 and part 3? 🙂
I keep coming back to this page, please keep it online forever!
Better than any Unity Documentation I’ve read!