はじめに
コルチーンの中でTask
を待つために以下のようなコードがあったとします。
private IEnumerator SampleCoroutine() { Debug.Log("Start Task"); // 1フレームだけ待つことに注意 yield return SampleAsync(); Debug.Log("EndTask"); } private async Task SampleAsync() { await Task.Delay(TimeSpan.FromSeconds(10)); }
一見ちゃんと動作するように見えますが、実はyield return SampleAsync()
は1フレームだけ待った後、続きが実行されます。
コンパイルエラーにならないので要注意です。
対応案1
簡単に考えられる対応としてTask.IsCompleted
がtrue
にまで、UnityEngine.WaitUntil
を利用することが挙げられます。
Task.IsCompleted プロパティ (System.Threading.Tasks) | Microsoft Learn
UnityEngine.WaitUntil - Unity スクリプトリファレンス
private IEnumerator SampleCoroutine() { Debug.Log("Start Task"); // 対応案1 Task task = SampleAsync(); yield return new WaitUntil(() => task.IsCompleted); Debug.Log("EndTask"); } private async Task SampleAsync() { await Task.Delay(TimeSpan.FromSeconds(10)); }
ただ大きな問題点として、SampleAsync
内でエラーが発生したとき、エラー出力されることなくtask.IsCompleted
がtrue
になってしまいます。
private async Task SampleAsync() { await UniTask.Yield(); // この時点でTask.IsCompletedがtrueになる、またエラーが伝播されない throw new NotImplementedException(); await UniTask.Yield(); }
一応エラーの情報はTask
内部に入っているので、調べることは可能ではあります。
var task = SampleAsync(); yield return new WaitUntil(() => task.IsCompleted); if(!task.IsCompletedSuccessfully) if (task.Exception != null) throw task.Exception;
対応案2
UniTask
を利用することで、async
からコルーチンに変換できます。
If you want to convert async to coroutine, you can use .ToCoroutine(), this is useful if you want to only allow using the coroutine system.
https://github.com/Cysharp/UniTask#install-via-git-url
private IEnumerator SampleCoroutine() { Debug.Log("Start Task"); // 対応案2 yield return SampleAsync().ToCoroutine(); Debug.Log("EndTask"); } private async UniTask SampleAsync() { await UniTask.Delay(TimeSpan.FromSeconds(2)); }
ToCoroutine
を利用した場合はちゃんとエラーが伝播されます。
またコールバックを追加することでエラーが発生したときの処理を追加することができます。
private IEnumerator SampleCoroutine() { Debug.Log("Start Task"); // SampleAsync内でエラーがあった場合、ログ出力をする yield return SampleAsync().ToCoroutine(exception => Debug.Log(exception.Message)); Debug.Log("EndTask"); } private async UniTask SampleAsync() { await UniTask.Yield(); throw new NotImplementedException(); }