【Unity】コルーチンのエラーハンドリングをやりやすくする拡張メソッド

Unityでコルーチンのエラーハンドリングをやりやすくする拡張メソッドをつくってみました。

Unity2020.2.7

課題

Unityのコルーチン内で例外がスローされると後続の処理は実行されません。

// この中で例外が発生したら
yield return StartCoroutine(routine);

// この後の処理は呼ばれない
Debug.Log("Finish.");

また、yieldはtry-catchの中には書けません。

try
{
    // こう書けない
    yield return StartCoroutine(routine);
}
catch (Exception e)
{
    // ハンドリング(できない)
}

これではエラーハンドリングしたいときに困るので、拡張メソッドで対応する方法を紹介します。

実装

拡張メソッドの実装は以下の通りです。

public static class MonoBehaviourExtensions
{
    /// <summary>
    /// コルーチンを開始する
    /// </summary>
    /// <param name="self"></param>
    /// <param name="routine"></param>
    /// <param name="throwException">発生した例外をスローするか</param>
    /// <param name="onError">例外発生時のコールバック</param>
    /// <returns></returns>
    public static Coroutine StartCoroutine(this MonoBehaviour self, IEnumerator routine,
        bool throwException = true, Action<Exception> onError = null)
    {
        return self.StartCoroutine(CreateRoutine(routine, throwException, onError));
    }
    
    private static IEnumerator CreateRoutine(IEnumerator routine, bool throwException = true,
        Action<Exception> onError = null)
    {
        while (true)
        {
            object current = null;
            Exception ex = null;
            try
            {
                if (!routine.MoveNext())
                {
                    break;
                }
                current = routine.Current;
            }
            catch (Exception e)
            {
                ex = e;
                onError?.Invoke(e);
                if (throwException)
                {
                    throw;
                }
            }

            if (ex != null)
            {
                yield return ex;
                yield break;
            }
            yield return current;
        }
    }
}

throwExceptionをfalseにすると発生した例外をせず、その時点でコルーチンの終了だけを行います。
またonErrorで例外発生時のコールバックを設定できます。

使う

この拡張メソッドは以下のようにして使います。

private IEnumerator Start()
{
    // 例外が発生してもスローせず、エラーログだけを出す
    yield return this.StartCoroutine(RoutineWithError(1.0f), false, Debug.LogError);

    // この処理も実行される
    Debug.Log("end");
}

private IEnumerator RoutineWithError(float durationSec)
{
    yield return new WaitForSeconds(durationSec);
    throw new Exception();
}