Coroutines are special types of function that do not need to complete execution in a single frame. Using the yield keyword, they can suspend their operation at any point until the next frame or until a few seconds have elapsed. However, unlike threading, they are a lightweight way of performing asynchronous tasks. In this article we’ll explore a few ways of using coroutines in Unity.

This article was available to $5+ Patreon supporters two weeks early.

Also check out a newer video about Coroutines over on YouTube:


Writing Coroutines

It’s possible to perform tasks over several frames by doing a little bit of the work each Update loop, but you’d have to store intermediate data inside a member variable or struct – it’d be very cumbersome. You might decide instead to set up a thread to do the concurrent work, but that’s very heavy-handed for most tasks and you must deal with thread-safety. Not only that but most of the Unity API is not thread-safe in the first place.

A coroutine is a Unity-specific feature that allows us to suspend execution for this frame and specify when execution should begin again. To use a function as a coroutine, it needs to have a specific return type – IEnumerator – and at least one suspension point within the function.

private IEnumerator DoTask()
{
    yield return null;
}

Ordinarily, a return statement would stop function execution completely and return control to the part of the code that called the function. In this example, we yield control to the part of code that called this coroutine, but control will be returned to the coroutine later. Returning null means that execution will resume next frame. To run a coroutine function, you can’t call it like a normal function – you must use the StartCoroutine function.

var routine = StartCoroutine(DoTask());

As you can see, the coroutine itself can be stored in a variable. Its type is Coroutine. By saving it in a member variable, we could access it somewhere else in the code in order to halt its execution – we use StopCoroutine for this.

StopCoroutine(routine);

If we wish to stop executing every Coroutine started on this MonoBehaviour, then we may use the StopAllCoroutines function.

StopAllCoroutines();

Now we’ve seen the basics of how to create, start and stop a coroutine, let’s look at some use cases.

Controlling a loop

We can use coroutines to split executions of a loop over a few frames. For example, if we wish to use a Lerp function to control the position of an object over time, we may use a Coroutine together with yield return null.

private IEnumerator DoTask()
{
    float startXPos = 3.0f;
    float endXPos = 10.0f;

    Vector3 position;

    for (float t = 0.0f; t < 5.0f; t += Time.deltaTime)
    {
        position = transform.position;
        position.x = Mathf.Lerp(startXPos, endXPos, t / 5.0f);
        transform.position = position;

        yield return null;
    }

    position = transform.position;
    position.x = endXPos;
    transform.position = position;
}

The inside of the loop executes until 5 seconds have elapsed because the loop counter is incremented by Time.deltaTime each frame. After the loop finishes, a few extra lines are added to set the desired object position in case the loop timer under-shoots slightly. This is a common idiom for animating values in code in Unity.

Adding a delay

There are many ways of adding a delay before carrying out a function in Unity. One such method is the Invoke function.

private void Start()
{
    Invoke("MyFunc", 5.0f);
}

private void MyFunc()
{
    // Do something here
}

In this case, we may use Invoke to call MyFunc with a delay of 5 seconds. However, this can’t be used to call functions with parameters. For that, we should instead use a coroutine and yield on a WaitForSeconds enumerator within the coroutine.

private void Start()
{
    StartCoroutine(MyFunc2(1, 5.0f));
}

private IEnumerator MyFunc2(int param1, float waitTime)
{
    yield return new WaitForSeconds(waitTime);
}

An extra benefit of using a coroutine with WaitForSeconds over an Invoke is that WaitForSeconds is affected by changes to Time.timeScale, while Invoke is not. If you wish to use a coroutine that is not affected by the time scale, Unity supplies WaitForSecondsRealtime as an alternative. WaitForSeconds is a class, so we create an instance using the new keyword; this means we can assign WaitForSeconds instances to a variable and reuse them in cases where the delay time is constant. This avoids creating excess garbage, especially in the case where a yield is used within a loop; garbage collection was discussed in the previous Unity Tips article.

private WaitForSeconds waitFiveSeconds = new WaitForSeconds(5.0f);

private void Start()
{
    StartCoroutine(MyFunc2(1, 5.0f));
}

private IEnumerator MyFunc2(int param1, float waitTime)
{
    yield return waitFiveSeconds;
}

Coroutine chaining

It’s possible to yield one coroutine in order to wait for another to execute and exit. Classes such as WaitForSeconds inherit from a class called YieldInstruction, which in turn inherits from IEnumerator – the same as the return type of all coroutines. That’s useful, because we can use a call to StartCoroutine in place of a YieldInstruction.

private WaitForSeconds waitFiveSeconds = new WaitForSeconds(5.0f);

private void Start()
{
    StartCoroutine(MyFunc1());
}

private IEnumerator MyFunc1()
{
    Debug.Log("MyFunc1: started.");
    yield return StartCoroutine(MyFunc2(1, 5.0f));
    Debug.Log("MyFunc1: finished MyFunc2.");
    yield return waitFiveSeconds;
    Debug.Log("MyFunc1: waited 5 seconds.");
}

private IEnumerator MyFunc2(int param1, float waitTime)
{
    Debug.Log("MyFunc2: started.");
    yield return waitFiveSeconds;
    Debug.Log("MyFunc2: waited 5 seconds.");
}

This also demonstrates the usage of more than one yield statement in series. When the Start function executes normally, it starts the MyFunc1 coroutine. After logging a debug message, it yields on the MyFunc2 coroutine, which then halts for 5 seconds before exiting as normal. When it exits, function execution resumes on MyFunc1 at the point where it yielded on MyFunc2 – it waits a further 5 seconds before it exits itself. Running this code and looking at the console log would confirm the order of operations.

Unity callbacks as Coroutines

So far, you’ll have seen Unity callback message functions such as Start, Awake or Update have a void return type – under normal circumstances, they all do. Some – not all – of them can have a return type of IEnumerator; if they do, Unity will automatically call them as a coroutine. That means we can use yield statements directly in those functions instead of starting separate coroutines. In previous examples, I’ve shown how to start a coroutine that waits five seconds from the Start function, but we can roll the functionality directly into Start itself.

private WaitForSeconds waitFiveSeconds = new WaitForSeconds(5.0f);

private IEnumerator Start()
{
    yield return waitFiveSeconds;
}

Functions tied to an update loop of any kind typically can’t be labelled as coroutines – that includes Update, FixedUpdate, LateUpdate and OnGUI. Nor can Awake or OnEnable become coroutines, or OnDisable, or OnDestroy. However, many functions not tied to the object’s lifecycle, such as OnCollisionXXX, OnTriggerXXX or OnMouseXXX, can return an IEnumerator. Check the Messages subsection of the MonoBehaviour Scripting API if you’re unsure whether a function can be a coroutine.

It’s best practice to use Start for initialising an instance of a class, so this functionality is great when your initialisation requires a few frames. Outside of setup, you’d be best off using StartCoroutine inside a void-returning Start function to maintain code readability. The same goes for other callbacks – use coroutines wisely!

Creating a fixed update loop

Unity comes with several update loops. Update and LateUpdate both happen once per frame and are dependent on the framerate, and FixedUpdate runs just before the internal physics update and attempts to run with a fixed interval between calls – it could run more than once per frame.

Unity callbacks have a defined order of operations, as seen on the Unity Order of Event Functions flowchart. It’s best to keep physics-based operations in FixedUpdate and input-reading in Update for that reason – a program designed with those guidelines synchronises well with Unity’s internal physics update and input-checking – but what if we wanted to run some code at a regular interval, without running each and every frame and not tied to FixedUpdate?

Looking at the diagram, there is a defined point at which control returns to a coroutine based on the type of YieldInstruction it uses; most are right after Update. If we had expensive code that only needs to update every second or so, we could check a timer each Update, but this bloats up the Update loop and makes the code harder to read; alternatively, we can set up our own coroutine-based update loop and decouple it from the regular Update function.

private void Start()
{
    StartCoroutine(UpdateEachSecond());
}

private IEnumerator UpdateEachSecond()
{
    var wait = new WaitForSecondsRealtime(1.0f);

    while(true)
    {
        // Do expensive operations here.

        yield return wait;
    }
}

This is a great demonstration of where you would use WaitForSecondsRealtime over WaitForSeconds – we don’t want the loop to be influenced by Time.timeScale changing. Strictly speaking, this may not provide exactly the desired behaviour; Update automatically stops executing on inactive objects, but coroutines do not. If we must ensure the coroutine stops running, we could start it when OnEnable is triggered instead of on Start and stop it when OnDisable is called.

private Coroutine updateEachSecond;

private void OnEnable()
{
    updateEachSecond = StartCoroutine(UpdateEachSecond());
}

private void OnDisable()
{
    StopCoroutine(updateEachSecond);
    updateEachSecond = null;
}

private IEnumerator UpdateEachSecond()
{
    var wait = new WaitForSecondsRealtime(1.0f);

    while(true)
    {
        // Do expensive operations here.

        yield return wait;
    }
}

If the object is not frequently disabled and reenabled, this technique would be adequate. But beware – when OnDisable is called and the old coroutine is no longer needed, it will be marked for de-allocation by the memory manager and incur a garbage collection penalty. If this happens frequently, some other solution should be used.

Waiting for a download

If your game needs to download data from the internet, you wouldn’t want to stall until the download has finished. Thankfully, Unity provides a way of sending a web request and yielding on a coroutine until the download has completed.

private IEnumerator WaitForDownload(string url)
{
    var request = UnityWebRequest.Get(url);

    yield return request.SendWebRequest();

    // Do something when the server returns data.
}

The UnityWebRequest class provides an easy way to communicate via HTTP over the internet. The class resides in the UnityEngine.Networking namespace, so you’ll have to tell Unity you’re using it. The SendWebRequest function returns a special type of AsyncOperation, which itself inherits YieldInstruction – that means it can be yielded on. This is most useful if you need to download, say, entries on a leaderboard to display in-game – realtime communication between players would be completely infeasible using this method. For more information on how to use the data returned by the request, check the Unity documentation.


Custom YieldInstructions

This class inherits YieldInstruction. The idea behind this class is to expose only one property – keepWaiting – which, when it becomes false, makes any coroutine yielding on this instruction continue its execution. We define our own class extending CustomYieldInstruction and modify the value of keepWaiting in any way we wish. We could create a YieldInstruction that waits until a random number passes a threshold; the value of keepWaiting is polled every frame between Update and LateUpdate so we can implement some behaviour in its getter.

public class WaitForRandomChance : CustomYieldInstruction
{
    private float threshold = 0.0f;

    public override bool keepWaiting
    {
        get
        {
            return Random.value <= threshold;
        }
    }

    public WaitForRandomChance(float threshold)
    {
        this.threshold = threshold;
    }
}

Using this instruction, we can write a coroutine that has a 25% chance of resuming execution each frame.

private IEnumerator MyFunc()
{
    yield return new WaitForRandomChance(0.25f);
}

It’s as easy as that! You could go further and run any code you wish in the keepWaiting getter but take care not to perform expensive checks because it will be run each frame. Another great example of when to use a CustomYieldInstruction is waiting for user input; the Unity documentation entry for CustomYieldInstruction gives the example of waiting for the right mouse button to be pressed, but we could extend that to any button or key input, or any mouse button.

public class WaitForButtonDown : CustomYieldInstruction
{
    private string buttonName;

    public override bool keepWaiting
    {
        get
        {
            return !Input.GetButtonDown(buttonName);
        }
    }

    public WaitForButtonDown(string buttonName)
    {
        this.buttonName = buttonName;
    }
}

Even some of the newer instructions provided by Unity by default inherit from CustomYieldInstruction. One is WaitForSecondsRealtime, which we saw earlier. There are two powerful instructions – WaitWhile and WaitUntil – which accept an arbitrary Boolean delegate and return control when the Boolean becomes false or true respectively. We could replace the WaitForButtonDown example using WaitUntil.

private IEnumerator MyFunc(string buttonName)
{
    yield return new WaitUntil(() => Input.GetButtonDown(buttonName));
}

A discussion about custom instructions in coroutines can be found on the Unity Blog.


What Coroutines are NOT for

For all the benefits of coroutines, there are scenarios which are not suited to them. The most important distinction to draw is that they are NOT the same as threads. By implementing threading into your program, you split up work between different CPU cores – threading runs code in parallel, so you may move expensive operations such as AI or network communication off the main thread and maximise CPU throughput.

However, the Unity API is not thread-safe and you should call Unity-specific functions from the main thread. This is where coroutines have the edge over threading because they run on the main thread, albeit differently to regular code. But on the other hand, blocking code implemented in a coroutine still blocks the main thread, so threading would be more suitable for solving this problem.


Coming Up

The next article in this series, which will be published on September 2nd, will be about Interpolation! It is already available in PDF format for $5 Patreon backers.


Acknowledgements

This content was a timed exclusive for my Patreon $5+ backers. I’d like to thank my $5 and $20 tier backers for making this content possible!

$20 Backers

Special thanks to my $20 backers:

  • Gemma Louise Ilett