【Unity】【C#】Taskの未処理例外をUnobservedTaskExceptionで処理してログ出力する

UnityでTaskの未処理例外をUnobservedTaskExceptionで処理する方法をまとめました。

Unity2020.1.10

非同期タスクを待機する場合は問題なし

まず非同期タスクをawaitで待機する場合には同期メソッドと同じようにtry-catchでハンドリングできます。
またUnityのConsoleにエラーログとして例外の内容が出力されます。

using System;
using System.Threading.Tasks;
using UnityEngine;

public class Example : MonoBehaviour
{
    private async void Start()
    {
        // 例外の内容のエラーログが出力される
        // try-catchでハンドリングできる
        await SomeProcessAsync();
    }

    private async Task SomeProcessAsync()
    {
        await Task.Run(() => throw new Exception("ExampleException")).ConfigureAwait(false);
    }
}

Wait()やResultした時も同様です。

SomeProcessAsync().Wait();

非同期タスクを待機しないと未処理例外が発生する

一方で、非同期タスクを待機しない場合にはtry-catchできません。
またエラーログも出力されず、このタスク内でスローされた例外は未処理例外となってしまいます。

using System;
using System.Threading.Tasks;
using UnityEngine;

public class Example : MonoBehaviour
{
    private async void Start()
    {
        // エラーログが出力されない
        // try-catchもできない
        var _ = SomeProcessAsync();
    }

    private async Task SomeProcessAsync()
    {
        await Task.Run(() => throw new Exception("ExampleException")).ConfigureAwait(false);
    }
}

未処理の例外をハンドリングする

このような未処理例外をハンドリングするにはTaskScheduler.UnobservedTaskExceptionにコールバックを登録します。
するとGCが発生したタイミングで未処理例外がこのコールバックに渡されます。

using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Example : MonoBehaviour
{
    private async void Start()
    {
        TaskScheduler.UnobservedTaskException += HandleUnobservedTaskExceptions;

        var _ = SomeProcessAsync();
        
        // タスク完了待ち
        Thread.Sleep(100);
        // GC発動
        GC.Collect();
    }

    private async Task SomeProcessAsync()
    {
        await Task.Run(() => throw new Exception("ExampleException")).ConfigureAwait(false);
    }
    
    private static void HandleUnobservedTaskExceptions(object sender, UnobservedTaskExceptionEventArgs e)
    {
        foreach (var innerEx in e.Exception.Flatten().InnerExceptions)
        {
            Debug.LogError(innerEx);
            throw innerEx;
        }
        // 処理済みとしてマークする
        e.SetObserved();
    }
}

これでTaskの未処理例外がエラーログとして出力されるようになりました。