Care With Coroutines

When a new Unity developer comes across a coroutine for the first time, it can be a bit of an intimidating experience. Understanding how coroutines work is a process that probably goes from: “I have no idea what is going on here,” to “I think I understand how these things work although it still seems like magic,” to “I’m pretty sure I love coroutines and I’m going to use them all of the time,” and finally, to “I’m realizing that I overuse coroutines, or at least have used them when I shouldn’t have.” (This progression is completely hypothetical, but I can promise that it represents a population of at least one aspiring Unity developer.)

The intention of this post is neither to describe how coroutines work or provide a list of when to use (and/or avoid using) them. (For a good explanation on how they work, see here.) However, I do want to illustrate one very simple scenario that at the very least will emphasize the need for caution.

The Unity documentation on coroutines uses the example of a Fade function that reduces an object’s opacity over time:

Although there are potential issues with the nature of this Fade function in-and-of-iself, it’s pretty clear that a material’s color will become more transparent as all game objects are updated. But consider what would happen if this coroutine were invoked a second time while the first invocation is still in process. It is not difficult to see that the material’s opacity could get reset to 1 by the second invocation of the coroutine.

You can test this out with the following modification to the above code, and add it to any MonoBehavior script you’ve already created:

You could see output like the following:

1: anotherFloat = 1
1: anotherFloat = 0.9
1: anotherFloat = 0.8
2: anotherFloat = 1
1: anotherFloat = 0.7
2: anotherFloat = 0.9
2: anotherFloat = 0.8

When applied to the situation where you are trying to reduce an object’s opacity, the results are not what we would intend.

Again, this is a very basic example. You might read it and think, “obviously I would never do something like that.” But the point isn’t really the example, but the principle that care should be taken when using coroutines. Especially in team environments, it is important to communicate the appropriate use of coroutines through comments or even verbal(!) communication, since team members may accidentally invoke already-created coroutines inappropriately. Also, I’m sure we’ve all come across situations where coroutines have been given much larger visibility than required. Another challenge here is that an external class instance could invoke the coroutine when it has already been invoked by the encapsulating class instance (and was only ever intended to be used in that manner), and thus produce undesirable results. In other words, don’t forget to make private coroutines private (although, even this doesn’t help in cases where public methods call private coroutines!), and structure your code in such a way that only allows coroutines to be called when they should be called.

The same principle applies to InvokeRepeating. I’ve come across situations where I’ve invoked a function on some repeating interval, but then mistakenly invoked that same function from somewhere else in the project while the function was already being called on a repeating basis. Especially if the function is modifying an instance variable or some property, you can very easily set up all kinds of race conditions that can make debugging a nightmare.

I’m sure there are examples out there that illustrate the need for care in these situations. I welcome any illustrations or examples that you’ve found helpful in establishing best practices for coroutine-use in team environments.

-Joel G.