Mastering Coroutines in Unity

Sooner or later, programmers learn about the existence of advanced programming techniques and features of great power that can help them do wonders. Coroutines are among those things.

This post is meant for anyone wanting to use them or already using them in Unity (using C#). Instructions and syntax may vary in other languages and contexts.

Brief introduction

Contrary to the standard functions supported in C#, whose scope and execution is limited to the specific instance they were run from, coroutines can be used for two reasons: asynchronous code and code that needs to compute over several frames.

Being Unity games single-threaded, coroutines can be used to simulate threaded behaviours. They aren’t, by any means, a perfect substitution, though; the game still runs under a single thread and coroutines are called one after another.

They are meant for code that could be run in Update() but either should be executed on demand (and not polled under an if every single time) or will run at a different frequency (every 5 seconds instead of every frame, for example).

Anatomy of a coroutine

While typical functions can return any type, coroutines must return an IEnumerator.

They can have parameters, just like any other function (with a caveat, but more on that later), and must use a special keyword before any return instruction.

C#

1

2

3

4

5

6

7

usingSystem.Collection;//Needed to use IEnumerators

protectedIEnumerator BasicCoroutine()

{

Debug.Log("Hello world!");

yield returnnull;//'yield' must be used before any "return"

}

In order to use them over several frames, a while or for instruction can be used, together with a yield. For example, the Update() function could be simulated with:

C#

1

2

3

4

5

6

7

8

9

10

usingSystem.Collection;

protectedIEnumerator PseudoUpdate()

{

while(true)

{

Debug.Log("Hello world!");

yield returnnull;

}

}

Whenever a yield is reached, the execution is halted and resumed later, depending on what type of yield was done.

How to start and stop them

There are two ways to execute coroutines in Unity, both using the StartCoroutine function. Both of them can only be stopped the same way they were started.

StartCoroutine(string s)

Like in many other places within Unity, strings can be used to indicate the name of a resource. In this case, coroutines can be started by using their name.

However, this solution is generally not the best one for two reasons:

Strings are prone to human errors; typos aren’t catched by the compiler.

This method only works for coroutines that have no input parameters.

C#

1

2

3

4

5

6

7

8

9

10

11

12

protectedvoidFunctionA()

{

StartCoroutine("BasicCoroutine");

}

protectedvoidFunctionB()

{

StopCoroutine("BasicCoroutine");

}

protectedIEnumerator BasicCoroutine()

{...}

Just beware that if the same coroutine is called more than once using this method, then using StopCoroutine will result in all of them being stopped (as the system doesn’t know which one specifically is the programmer referring to).

StartCoroutine(IEnumerator e)

They can also be started by explicitly calling them inside StartCoroutine. This approach does allow it to have input parameters.

C#

1

2

3

4

5

6

7

protectedvoidFunctionA()

{

StartCoroutine(BasicCoroutine(0));

}

protectedIEnumerator BasicCoroutine(intx)

{...}

Stopping this kind of coroutines is tricky. Intuition might suggest doing StopCoroutine(BasicCoroutine(0)); however, doing so would end up using a different instance of BasicCoroutine and not the one previously started.

Subsequent calls of line #2 will replace previous instances of such coroutine.

There’s a way to stop all coroutines running inside a MonoBehaviour, disregarding whether they were started using one method or the other, using the StopAllCoroutines() funtion.

Also, there’s the yield break command available to completely stop the current coroutine from inside.

Optimizing their use

Check the following code. How can we improve its performance?

C#

1

2

3

4

5

6

7

protectedIEnumerator Test()

{

while(true)

{

yield returnnewWaitForEndOfFrame();

}

}

It turns out that, every time the coroutine yields, it needs to allocate memory for a new object of type WaitForEndOfFrame.

Instead, we should cache it and yield-return it when needed.

C#

1

2

3

4

5

6

7

8

9

protectedIEnumerator Test()

{

WaitForEndOfFrame wait=newWaitForEndOfFrame();

while(true)

{

yield returnwait;

}

}

This is only necessary for yields that return any value other than null.

Another common mistake is using yield return 0. While it works like yield return null, it «allocates unwanted memory, due to unboxing the 0 to null. So you should use “yield return null;” to lessen GC stress» according to user Loofou on a tutorial about Coroutines at UnityPatterns (link no longer working; removed). In order words, when in doubt, use yield return null.

In conclusion

Coroutines are not cheap but, if used correctly, can be a great tool. Just keep in mind that they aren’t silver bullets.

Remember to improve the performance of your code by avoiding unnecessary memory allocations and use the different ways available to start them depending on your necessities.