C# supports iterator functions with the yield keyword. Unity uses them to support coroutines. Unfortunately, both are poorly understood by many Unity programmers. Today’s article dives into iterator functions and coroutines to better understand how they work and what they can be used for. Read on to learn how to use iterator functions and coroutines for more effective asynchronous code!

First a bit of terminology. An iterator function is a function that uses the yield return or yield break statements. In order to use these, it must return one of the following types:

  • System.Collections.IEnumerator
  • System.Collections.Generic.IEnumerator<T>
  • System.Collections.IEnumerable
  • System.Collections.Generic.Enumerable<T>

Iterator functions are similar to normal functions, but with some key differences. Consider a simple iterator function:

IEnumerator Foo()
{
    Debug.Log("Beginning of Foo()");
    yield return "a";
    Debug.Log("Middle of Foo()");
    yield return "b";
    Debug.Log("End of Foo()");
}

When you call Foo(), the function does not immediately execute. The Debug.Log() function at its beginning will not be executed. Instead, you get back an IEnumerator which can be used to execute the function. Here’s how that looks:

// Get an enumerator for Foo, but don't start it
IEnumerator e = Foo();
 
// Foo has not yet begun

This alone can lead to quite a few mistakes. Normally calling a function executes it, but with an iterator function you won’t receive any warning or error telling you that you haven’t really executed the function.

Now that we have the enumerator we can use it to begin executing the function. To do so, call the enumerator’s MoveNext() like so:

IEnumerator e = Foo();
bool functionHasMore = e.MoveNext(); // prints: "Beginning of Foo()"

If the MoveNext() function returns true, the iterator function has more code to execute before it’s done. This also means that there was a return value from yield return that we can check using .Current:

IEnumerator e = Foo();
bool functionHasMore = e.MoveNext();
if (functionHasMore)
{
    Debug.Log("Iterator function returned: " + e.Current);
}

You’re free to stop calling MoveNext() for now and continue calling it later. This is what Unity does when it calls your coroutines once per frame. If you’d rather go through the whole iterator function all at once, you can do so easily with a loop:

for (var e = Foo(); e.MoveNext(); )
{
    Debug.Log("Iterator function returned: " + e.Current);
}

To make that even easier, change the return type of the iterator function to IEnumerable instead of IEnumerator:

IEnumerable Foo()
{
    Debug.Log("Beginning of Foo()");
    yield return "a";
    Debug.Log("Middle of Foo()");
    yield return "b";
    Debug.Log("End of Foo()");
}

Now you can use a foreach loop:

foreach (var cur in Foo())
{
    Debug.Log("Iterator function returned: " + cur);
}

Unfortunately, Unity will only accept functions returning IEnumerator or IEnumerator<T> as coroutines, so you can’t always use IEnumerable or IEnumerable<T>.

What if you want to end an iterator function before it naturally ends? With a normal function you could just add a return; statement. With an iterator function, yield break; takes its place. Here’s a simple example of an iterator function that returns random array elements unless the array is null:

IEnumerator RandomElements(int[] array, int num)
{
    if (array == null)
    {
        yield break;
    }
 
    var random = new Random();
    for (var i = 0; i < num; ++i)
    {
        var index = random.Next(array.Length);
        yield return array[index];
    }
}

With this knowledge of iterator functions as a foundation, let’s see if we can nest Unity coroutines inside of Unity coroutines. We’ll pass one iterator function—Outer()—to StartCoroutine() and then call other iterator functions—Inner1 and Inner2— from Outer().

Will this properly spread the work out across frames? To find out, consider the following MonoBehaviour that prints the frame number with each line of each function. What do you think it will print?

using System;
using System.Collections;
using System.Collections.Generic;
 
using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	string report = "";
 
	void Start()
	{
		Log("Before StartCoroutine()");
		StartCoroutine(Outer());
		Log("After StartCoroutine()");
	}
 
	void Log(string msg)
	{
		report += Time.frameCount + ": " + msg + "\n";
	}
 
	IEnumerator Outer()
	{
		Log("Beginning of Outer()");
		for (var e = Inner1(); e.MoveNext(); )
		{
			yield return e.Current;
		}
		Log("Middle of Outer()");
		for (var e = Inner2(); e.MoveNext(); )
		{
			yield return e.Current;
		}
		Log("End of Outer()");
 
		Debug.Log(report);
	}
 
	IEnumerator Inner1()
	{
		Log("Beginning of Inner1()");
		yield return "a1";
		Log("Middle of Inner1()");
		yield return "b1";
		Log("End of Inner1()");
	}
 
	IEnumerator Inner2()
	{
		Log("Beginning of Inner2()");
		yield return "a2";
		Log("Middle of Inner2()");
		yield return "b2";
		Log("End of Inner2()");
	}
}

Here’s what Unity actually prints with analysis by me:

// Script begins on frame 1
1: Before StartCoroutine()
 
// StartCoroutine calls MoveNext() immediately
1: Beginning of Outer()
 
// Outer() calls MoveNext() immediately on Inner1()
1: Beginning of Inner1()
 
// StartCoroutine() does not block
1: After StartCoroutine()
 
// Next frame Unity calls MoveNext() on Outer()
// Outer() calls MoveNext() on Inner1()
2: Middle of Inner1()
 
// Next frame Unity calls MoveNext() on Outer()
// Outer() calls MoveNext() on Inner1()
3: End of Inner1()
 
// MoveNext() on Inner1() returned false so Outer() exits the first loop
3: Middle of Outer()
 
// Outer() calls MoveNext() immediately on Inner2()
3: Beginning of Inner2()
 
// Next frame Unity calls MoveNext() on Outer()
// Outer() calls MoveNext() on Inner2()
4: Middle of Inner2()
 
// Next frame Unity calls MoveNext() on Outer()
// Outer() calls MoveNext() on Inner2()
5: End of Inner2()
 
// MoveNext() on Inner2() returned false so Outer() exits the second loop
5: End of Outer()

As you can see, it’s easy to nest coroutines in Unity. Just make sure that your calls to nested iterator functions are done properly as a loop with a yield return on each iteration rather than a single function call.

This capability allows us to write much simpler asynchronous code. We don’t need to split our logic into two functions- one that initiates the asynchronous operation and one that handles its completion. For example, imagine that Unity’s WWW class was not treated specially and you couldn’t simply wait for it to finish by yield return WWW. Here’s how your code would look:

class MyScript : MonoBehaviour
{
    WWW www;
 
    void Start()
    {
        www = new WWW("http://mysite.com/file");
    }
 
    void Update()
    {
        if (www != null && www.isDone)
        {
            // Handle download being done
 
            // Remember to null the WWW so you don't handle it begin done again
            www = null;
        }
    }
}

Or you could use coroutines to radically clean up the code:

class MyScript : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(StartDownload("http://mysite.com/file"));
    }
 
    IEnumerator StartDownload(string url)
    {
        var www = new WWW(url);
        while (www.isDone == false)
        {
            yield return null;
        }
        // Handle download being done
    }
}

There’s no need for a temporary field to store, remember to set to null, or get overwritten by concurrent downloads. There’s no need for an Update() to check if the operation is done every frame. Instead, the whole operation is neatly contained inside one simple iterator function used as a coroutine.

While WWW is treated specially by Unity, many other asynchronous operations are not. Consider a coroutine next time you need to do something asynchronously and remember that you can easily nest them with the techniques from this article.

If you’ve got any other strategies for asynchronous operations, coroutines, or iterator functions, feel free to comment and let me know!