Catching Exceptions in Coroutines
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.
#1 by Pierre-Luc on July 11th, 2017 ·
Very good job, thank you!
#2 by Horace on November 21st, 2017 ·
Hi Jackson,
Can you tell me why there is both
break;
and
yield break;
in the throwing iterator loop?
(And thanks for the awesome articles!)
#3 by jackson on November 21st, 2017 ·
Hey Horace,
The two statements are confusingly similar as they both include the word “break.” They actually have very different behaviors. The
break
statement is just what you’re used to normally with loops. It moves the point of execution to after the loop, essentially “breaking out” of the loop by skipping the rest of it and the next loop condition check.The
yield break
statement is like a version ofbreak
for iterators. It ends the iterator function immediately just like if the function’s execution hit the end. It’s more likereturn
in a non-iterator function, but unfortunately they named another statementyield return
so they couldn’t use that. Again, very confusing.In this particular loop I used
break
when the iterator being run signals that it’s done by returningfalse
fromMoveNext
. That’ll happen when the iterator hits the end of the function or callsyield break
. When the iterator is done, I want to stop the loop and go straight to the last line of the function which calls thedone
parameter withnull
to indicate that the iterator function was not done because it threw an exception.Then I used
yield break
when an exception is caught. I used this right after I called thedone
parameter with the exception that was thrown by the iterator function. I usedyield break
because I wanted to end the function immediately rather than continuing its execution after the loop, which is whatbreak
would do. If I usedbreak
then the function would have continued after the loop by calling thedone
parameter again withnull
. That would have signaled the user that the iterator was done twice and that it was done with both an exception and not an exception, clearly not what the function should be doing.It’s tricky code, so it definitely takes a bit of examination. Hopefully that clears things up.
#4 by Horace on November 22nd, 2017 ·
Thanks, I see!
Thanks to you I was able to understand enough to make my own version of your function, which works slightly differently to suit my needs. It catches any
yield return
ed IEnumerators and iterates over them, instead of letting them “escape”.#5 by jackson on November 22nd, 2017 ·
I’m glad you were able to tweak it into something that works for you. One little tip: the
is
check followed by anas
cast at at the end can be simplified to avoid double type-checking:#6 by Horace on November 22nd, 2017 ·
(The formatting chewed some of my code, but not too much is missing.)
#7 by hunbuso on March 12th, 2018 ·
we encountered the same problem, can’t catch exception in Unity when start coroutine with a call back function.
Thanks very much, your article help me to solve our problem.
#8 by Mike on June 27th, 2018 ·
Thanks for writing the article, very useful technique.
For me, I start to find the intent of the code a little hard to read with the yields and enumerators.
I usually end up wrapping my functional code (the file download) in a thread-calling-class and then then just check a finished flag in the thread-calling-class in the Update() of the calling monobehavior to see when its finished. Then check to see if an exception has been caught by wrapping the functional code in a try catch on the other thread and recording any exceptions thrown in the thread-calling-class (which is a member variable of the calling monobehavior). The downside is you cannot use anything that requires the main thread.
#9 by jackson on June 28th, 2018 ·
That’s understandable, especially given what goes on behind the scenes to make iterator functions happen. A hand-coded state machine can be more clear to some readers, more flexible, and more efficient.
Threading is fundamentally different than coroutines though, as I’m sure you know. The lack of Unity API access make it harder to use them, but it’s getting easier now that the Job System has been introduced. See my tutorial for more information on what you can and can’t use from off the main thread, which also applies to any other non-job threads.
#10 by Eugene on May 3rd, 2020 ·
Hi Jackson,
thank you for the article! I tried to use your code and it didn’t work in my project.
I started to dig deeper and tested it in a separate clean project and it behaved just as expected.
The difference between projects is that in my project I call StartThrowingCoroutine() from inside of a callback of async method sceneInstance.ActivateAsync(). It looks the ActivateAsync() uses coroutines inside of it so when the exception is thrown by my code, Unity simply terminates the ActivateAsync() coroutine and all my code together with it.
Any ideas on how to deal with the case?
#11 by Eugene on May 3rd, 2020 ·
It seems Horace’s fix (https://jacksondunstan.com/articles/3718/comment-page-1#comment-698215) solved my issue.
#12 by LAFON Sylvain on August 25th, 2020 ·
I prefer doing it like this:
Then calling the risky routine this way: