Coroutine and Iterator Function Tricks
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!
#1 by Yohan Hadjedj on April 27th, 2015 ·
Thanks for sharing! Great :)
#2 by Dom Regan on March 3rd, 2016 ·
Thanks for sharing, this was very informative. I’ve pasted an example I created below from Unity that I found helpful to understand co-routines further. Using
yield return StartCoroutine(Inner());
void Start()
{
StartCoroutine(Outer());
}
IEnumerator Outer()
{
yield return new WaitForSeconds(30.0f); // Skip startup noise
Debug.Log(“Start of Outer”);
yield return StartCoroutine(Inner());
Debug.Log(“End of Outer”);
}
IEnumerator Inner()
{
Debug.Log(“Start of Inner”);
yield return new WaitForSeconds(3.0f);
Debug.Log(“Middle of Inner”);
yield return new WaitForSeconds(3.0f);
Debug.Log(“End of Inner”);
}
it outputs
Start of Outer
Start of Inner
Middle of Inner
End of Inner
End of Outer
#3 by Muhammad Heydari on April 3rd, 2016 ·
how can I stop a WaitForSeconds in an IEnumerator function.
I call it from StartCoroutine and in that IEnumerator function I need to break an WaitForSeconds by anykey
#4 by jackson on April 3rd, 2016 ·
Once you’ve yielded it, you can’t interrupt it. Instead, you might make your own version using
Time
,Stopwatch
, orDate
and either a custom iterator function or the new CustomYieldInstruction in 5.3.#5 by Zino on October 15th, 2017 ·
Thank you for sharing , it was great to know all these stuffs.
I want to know if there is a way to know if a coroutine is still running or not.
#6 by jackson on October 15th, 2017 ·
MoveNext
will returntrue
if the iterator is still running andfalse
if it’s done. For coroutines, you probably don’t want to callMoveNext
since that’s Unity’s job. Instead, you can work around this by explicitly setting some flag when the coroutine is done. For example:Alternatively, you could wrap an coroutine in a coroutine like this:
Hopefully one of these strategies is useful to you.