Unity code frequently makes use of the coroutine feature of MonoBehaviour. It can make asynchronous code a lot easier to write, but runs into problems when exceptions are thrown. There’s no avoiding exceptions since they’re built into C# (e.g. NullReferenceException) but we can cope with them, even when they’re in coroutines. Today’s article introduces a helper function or two that you can drop into your projects to help you handle exceptions thrown from your coroutines. Read on to learn how!

First let’s look at the kind of problem we’re looking to solve. Simply put, it’s any coroutine that throws an exception! For example, say you’re trying to write a function that calls a callback when some asynchronous operation is done. Here’s a function that downloads a file and stores it to disk:

using System.Collections;
using UnityEngine;
 
public static class WebDownloader
{
	public static void DownloadToFile(
		string url,
		string filePath,
		Func<IEnumerator, Coroutine> coroutineStarter,
		Action<bool> callback
	)
	{
		coroutineStarter(Download(url, filePath, callback));
	}
 
	private static IEnumerator DownloadUrl(
		string url,
		string filePath,
		Action<bool> callback
	)
	{
		var www = new WWW(url);
		yield return www;
		if (string.IsNullOrEmpty(www.error))
		{
			File.WriteAllBytes(filePath, www.bytes);
			callback(true);
		}
		else
		{
			callback(false);
		}
	}
}

And here’s some code that uses it:

using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	void Start()
	{
		WebDownloader.DownloadToFile(
			"http://test.com/path/to/asset",
			"/path/to/store",
			StartCoroutine,
			success => Debug.Log("download and store " + (success ? "success" : "failure"));
		);
	}
}

One problem is that File.WriteAllBytes might fail for a variety of reasons. There could be permissions issues or the path might be invalid, among various other problems. We might have also written any number of other bugs, even something as simple as a NullReferenceException. The point is: we want to call the user back no matter what happens or we’ll leave them hanging forever.

Throwing an exception from a coroutine terminates the coroutine. It doesn’t cause the app to crash because Unity implicitly catches all uncaught exceptions, logs them out, then continues the app. That leads to an interesting predicament. Since Unity is the one running the coroutine, the code that started the coroutine doesn’t even know the exception was thrown!

At this point you might be thinking that we should simply insert a try-catch block around the whole coroutine:

private static IEnumerator DownloadUrl(
	string url,
	string filePath,
	Action<bool> callback
)
{
	try
	{
		var www = new WWW(url);
		yield return www;
		if (string.IsNullOrEmpty(www.error))
		{
			File.WriteAllBytes(filePath, www.bytes);
			callback(true);
		}
		else
		{
			callback(false);
		}
	}
	catch (Exception ex)
	{
		callback(false);
	}
}

If you try that, you’ll be greeted by a compiler error:

error CS1626: Cannot yield a value in the body of a try block with a catch clause

You might be able to add several try-catch blocks around all the parts of your function except the yield return statements, but your function will soon become extremely long and the contortions required to make sure a yield return statement never throws can be very awkward.

Which leads us to the workaround that’s the point of today’s article. There are two techniques that we can combine to successfully catch the exception. First is to insert another iterator function in between the coroutine and Unity to catch the exception. An iterator function is any function that returns IEnumerator, IEnumerable, or their generic (<T>) equivalents. That means we end up with a function like this:

public static class CoroutineUtils
{
	public static IEnumerator RunThrowingIterator(IEnumerator enumerator)
	{
		foreach (var cur in enumerator)
		{
			yield return cur;
		}
	}
}

Then we use it like this:

using System.Collections;
using UnityEngine;
 
public static class WebDownloader
{
	public static void DownloadToFile(
		string url,
		string filePath,
		Func<IEnumerator, Coroutine> coroutineStarter,
		Action<bool> callback
	)
	{
		coroutineStarter(
			CoroutineUtils.RunThrowingIterator(
				Download(
					url,
					filePath,
					callback
				)
			)
		);
	}
}

We’ve basically wrapped our coroutine iterator function with another iterator function. Currently the wrapper just runs our coroutine iterator function, but now we have a middle layer we can use to catch the exception. So how do we do that? Well we already know that we can’t just try-catch the whole body of RunThrowingIterator because it too contains a yield return statement. Instead, we can use the second technique to break down the foreach so that we can catch just part of it:

public static class CoroutineUtils
{
	public static IEnumerator RunThrowingIterator(IEnumerator enumerator)
	{
		while (true)
		{
			if (enumerator.MoveNext() == false)
			{
				break;
			}
			var current = enumerator.Current;
			yield return current;
		}
	}
}

It’s certainly a lot more verbose than the foreach loop, but now we can see the hidden parts that foreach was doing for us: MoveNext to resume the function and Current to get the value passed to yield return. Technically, both of those can throw so we should catch everything but the yield return current; part of the while loop’s body:

public static IEnumerator RunThrowingIterator(
	IEnumerator enumerator
)
{
	while (true)
	{
		object current;
		try
		{
			if (enumerator.MoveNext() == false)
			{
				break;
			}
			current = enumerator.Current;
		}
		catch (Exception ex)
		{
			yield break;
		}
		yield return current;
	}
	done(null);
}

Now if the coroutine iterator function throws an exception at any point then RunThrowingIterator will catch it and immediately terminate. The final step is to notify users of RunThrowingIterator that the exception happened or that no exception happened at the time the coroutine iterator function finished. Unfortunately we can’t really return a value from an iterator function since that’s taken up already by the IEnumerator, but we can call a callback.

Here’s the final form of the utility code:

using System;
using System.Collections;
 
using UnityEngine;
 
/// <summary>
/// Utility functions to handle exceptions thrown from coroutine and iterator functions
/// http://JacksonDunstan.com/articles/3718
/// </summary>
public static class CoroutineUtils
{
	/// <summary>
	/// Start a coroutine that might throw an exception. Call the callback with the exception if it
	/// does or null if it finishes without throwing an exception.
	/// </summary>
	/// <param name="monoBehaviour">MonoBehaviour to start the coroutine on</param>
	/// <param name="enumerator">Iterator function to run as the coroutine</param>
	/// <param name="done">Callback to call when the coroutine has thrown an exception or finished.
	/// The thrown exception or null is passed as the parameter.</param>
	/// <returns>The started coroutine</returns>
	public static Coroutine StartThrowingCoroutine(
		this MonoBehaviour monoBehaviour,
		IEnumerator enumerator,
		Action<Exception> done
	)
	{
		return monoBehaviour.StartCoroutine(RunThrowingIterator(enumerator, done));
	}
 
	/// <summary>
	/// Run an iterator function that might throw an exception. Call the callback with the exception
	/// if it does or null if it finishes without throwing an exception.
	/// </summary>
	/// <param name="enumerator">Iterator function to run</param>
	/// <param name="done">Callback to call when the iterator has thrown an exception or finished.
	/// The thrown exception or null is passed as the parameter.</param>
	/// <returns>An enumerator that runs the given enumerator</returns>
	public static IEnumerator RunThrowingIterator(
		IEnumerator enumerator,
		Action<Exception> done
	)
	{
		while (true)
		{
			object current;
			try
			{
				if (enumerator.MoveNext() == false)
				{
					break;
				}
				current = enumerator.Current;
			}
			catch (Exception ex)
			{
				done(ex);
				yield break;
			}
			yield return current;
		}
		done(null);
	}
}

Finally, WebDownloader.DownloadToFile uses CoroutineUtils.RunThrowingIterator to guarantee its callback is called regardless of exceptions:

using System.Collections;
using UnityEngine;
 
public static class WebDownloader
{
	public static void DownloadToFile(
		string url,
		string filePath,
		Func<IEnumerator, Coroutine> coroutineStarter,
		Action<bool> callback
	)
	{
		coroutineStarter(
			CoroutineUtils.RunThrowingIterator(
				Download(
					url,
					filePath,
					callback
				),
				ex => callback(ex == null)
			)
		);
	}
}

As a bonus, I added in an extension function to make it even easier to use as a StartCoroutine replacement in your MonoBehaviour classes. Simply use it like this:

using System.Collections;
using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	void Start()
	{
		StartThrowingCoroutine(
			MyIterator(1),
			ex => {
				if (ex != null)
				{
					Debug.LogError("Coroutine threw exception: " + ex);
				}
				else
				{
					Debug.Log("Success!");
				}
			}
		);
	}
 
	private IEnumerator MyIterator(int i)
	{
		yield return null;
		if (i == 0) throw new Exception("0");
		yield return null;
		if (i == 1) throw new Exception("1");
		yield return null;
		if (i == 2) throw new Exception("2");
		yield return null;
		if (i == 3) throw new Exception("3");
		yield return null;
	}
}

And that’s all! Feel free to use these utility functions to handle exceptions in your coroutines or any other iterator functions.