UniRxやUniTaskなどを使った場合の横断的な例外処理についてのメモ書き。
基本的にUniRxでのExceptionは、ストリームが壊れないように堅牢な方向に機能が実装されている。ストリームの内から外へ例外を搬出するのは楽ではない。そういう閉じた世界(例えば画面単位など)で使うのがベターそう。
SubjectのSubscribeのonErrorコールバック引数
- ストリームで起きたエラーが入ってくる
- Subscribeで起きたエラーは入らない
.Subscribe(
_ => throw new Exception(),
e => Debug.Log("ここには来ない"));
前段にDoをかます
- 後段のSubscribeのonErrorに流れる
.Do(_ => throw new Exception()) .Subscribe( _ => {}, e => Debug.Log("ここには来る"));
- ただし、
Do(async _ => xxx)と非同期にしてると流れない - 間にObserveOnMainThreadしてもダメ
.Do(async _ => throw new Exception()) .Subscribe( _ => {}, e => Debug.Log("ここには来ない"));
ストリームからのthrow
throwしてもストリームの外には伝搬しない- 外で
UniTaskCompletionSourceで状態待機してる場合は、completed.TrySetException(e)で外のタスクに例外を起こさせることができる
.Do(_ => throw new Exception()) .Subscribe( _ => {}, e => _completed.TrySetException(e));
ReactivePropertyのストリーム
- Subjectと同じ
MessageBrokerでPub/Sub
- 使い方と使うとこ絞れば便利そう
// Pub MessageBroker.Default.Publish(new AbortApplicationException()); // Sub MessageBroker.Default.Receive<AbortApplicationException>() .Subscribe(_ => { Debug.LogError("MessageBroker.AbortApplicationException"); GoToTitle(); }) .AddTo(this);
Lopp + while のステートマシン
- 例外が自然に伝搬するのでこれが楽そう
private async UniTask MainLoop() { while(current != State.End) { switch(current) { // 各ステートの処理 case State.Start: await Hoge(); current = State.Next; break; } await UniTask.Yield(); } }
AsyncReactiveProperty
WaitAsyncだとタイミングで取りこぼして停止してしまう
private async UniTask MainLoop() { while(current.Value != State.End) { // 取りこぼして停止する可能性がある var next = await current.WaitAsync(); switch(next) { // 各ステートの処理 case State.Start: await Hoge(); current = State.Next; break; } } }
- UniTask.Linqで
Queueを挟んでメソッドチェーンであれば、例外の伝搬もされる
private async UniTask MainLoop() { await current .WithoutCurrent() // 初期値無視 .Queue() // 取りこぼし防止 .ForEachAwaitAsync(async x => { switch(x) { // 各ステートの処理 throw new Exception(); } }); }
UnhandledExceptionっぽいやつ
- 動かなかったり扱いが難しそうだったりで使えないかも
AppDomain.CurrentDomain.UnhandledException += (_, args) => Debug.LogError("AppDomain.CurrentDomain.UnhandledException " + args.ExceptionObject); UniTaskScheduler.UnobservedTaskException += exception => Debug.LogError("UniTaskScheduler.UnobservedTaskException " + exception); Application.logMessageReceivedThreaded += (condition, trace, type) => Debug.Log($"Application.logMessageReceivedThreaded {type}: {condition} ({trace})");
参考
- neuecc/UniRx: Reactive Extensions for Unity
- 【Unity】【UniRx】UniRxで例外を取り扱う方法まとめ - LIGHT11
- UniRxアンチパターン集 #Unity - Qiita
- UniRxのディープなつまずきの紹介 - Cluster Tech Blog
- UniRxのMessageBrokerが便利という話 #Unity - Qiita
- 【C#】ZeroMessenger – .NET/Unity向けの軽量メッセージングライブラリ - Annulus Games
- Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
- 【UniTask】UniTaskCompletionSourceを使って好きなタイミングで結果を確定させるUniTaskを生成する(ついでにUniTask.Voidの紹介) - はなちるのマイノート
- 【UniRx, UniTask】ReactivePropertyをawaitしたらどうなるか(awaitした後ReactivePropertyの値が変わるまで) - はなちるのマイノート
- UniTask Ver2 、UniTaskAsyncEnumerableまとめ #C# - Qiita
- UniTaskがパワーアップ!『UniTask v2』を使おう! - HackMD