Unity Coroutine Confusion

We've been delving into Coroutines in Unity lately, and ran into a strange behavior today that took all morning to sort out. In brief, a routine we thought we were calling never got called at all -- a Debug.Log as the very first line of the function never logged anything.

We finally traced it back to some misunderstanding about how Unity coroutines in C# operate, fueled by a mistake in the docs. This blog post will attempt to clear things up so you don't have to waste as much time as we did.

Let's start with the overview in the documentation. It includes this example:

public class example : MonoBehaviour {
	IEnumerator Do() {
		print("Do now");
		yield return new WaitForSeconds(2);
		print("Do 2 seconds later");
	}
	void Awake() {
		Do();
		print("This is printed immediately");
	}
}

This is a great example of what doesn't work, and in fact is a simplified version of what we were facing today. If you try this, you'll find that the Do() method never executes at all, and the lines it is printing never appear.

To make this work properly, you have to wrap the call to Do() in a StartCoroutine call, like this:

public class example : MonoBehaviour {
	IEnumerator Do() {
		print("Do now");
		yield return new WaitForSeconds(2);
		print("Do 2 seconds later");
	}
	void Awake() {
		StartCoroutine(Do());
		print("This is printed immediately");
	}
}

This example works as intended: when run, you see "Do now", followed by "This is printed immediately," and two seconds later, "Do 2 seconds later."

Now suppose you want the Awake routine to wait until the Do call is really done. You could try changing the StartCoroutine line to this:

yield return StartCoroutine(Do());

But this produces a compile-time error: "The body of `example.Awake()' cannot be an iterator block because `void' is not an iterator interface type." OK, says you, let's change the method declaration from "void Awake" to "IEnumerator Awake". But this just produces a different error: "Script error: Awake() can not be a coroutine." (This despite the documentation's claim that all event handlers can be coroutines except for Update and FixedUpdate.)

However, the Start event has no such limitations. So this does work:

public class example : MonoBehaviour {
	IEnumerator Do() {
		print("Do now");
		yield return new WaitForSeconds(2);
		print("Do 2 seconds later");
	}

IEnumerator Start() { yield return StartCoroutine(Do()); print("Do is done"); } }

Run this, and you see "Do now" right away, then two seconds later, "Do 2 seconds later" and "Do is done."

So, to recap:

  1. In C#, you must always call StartCoroutine around any IEnumerator method which you expect to actually execute. If you forget to do this, the method is simply not executed.

     

  2. The call to StartCoroutine returns right away. It does, however, return a Coroutine object, which you can pass to "yield return" if you want to wait for the called function to complete.

     

  3. This "yield return" trick works only in a method which is itself a coroutine. (You can tell these because they return IEnumerator.)

     

  4. Only certain MonoBehavior methods can be declared to return IEnumerator (and thus be coroutines).

     

  5. All these coroutine shenanigans apply only to MonoBehavior instances. They're built on standard C# constructs, but the Unity framework itself keeps track of coroutine objects, calls MoveNext on them each time through the event loop, and disposes of them when they're done. So, if you have some class that's not a MonoBehavior, and need to use coroutines, you'll need help from a MonoBehavior instance.

     

I hope this helps someone stumbling over this problem in the future. Now, if you'll excuse me, I'm off to file a bug report against the Unity documentation...