UniRxで例外を取り扱う方法をまとめました。
- UniRxによる例外処理の必要性
- Subscribeの引数のonErrorで処理する
- Catchオペレータで例外を補足する
- Catch時に後続処理が必要ない場合にはCatchIgnoreを使う
- エラーが起きた時に購読しなおすRetryオペレータ
- 特定の例外のみRetryするにはOnErrorRetryを使う
- 例外発生時に処理はするけど補足はしないDoOnError
- 関連
Unity2018.4.0
UniRxによる例外処理の必要性
まず、下記のように例外を発生させるだけのストリームを作ってみます。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .Subscribe() .AddTo(this); } }
これを再生してみます。
Unityで例外が発生するとConsoleにエラーが出ますが、これを再生してもエラーが出ないことがわかります。
これはUniRxどうこうではなく、単純にメインスレッド以外で例外が発生しているからです。
試しに上記のコードを下記のようにメインスレッド内で例外が発生するように書き換えてみます。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .ObserveOnMainThread() // メインスレッドで処理をする .Subscribe() .AddTo(this); } }
この状態で再生をするとConsoleにエラーが表示されることがわかります。
このように、UniRxは簡単にマルチスレッドの処理を書ける分、
エラーハンドリングはしっかりと行う必要があります(UniRxに限らずしっかりやらないとですが)。
そこで、この記事ではUniRxで例外を取り扱う方法をまとめます。
Subscribeの引数のonErrorで処理する
一番基本的な方法はSubscribeの引数のonError
に例外発生時の処理を書くことです。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .Subscribe ( _ => { }, ex => Debug.Log("例外発生 : " + ex) // onErrorに例外発生時の処理を書く ) .AddTo(this); } }
onErrorはストリーム内で例外が発生した時に例外のインスタンスと共に呼ばれます。
Catchオペレータで例外を補足する
Subscribe()
よりも前に例外処理を書きたい場合にはCatch()
を使います。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .Catch((Exception ex) => { // 例外が発生した時の処理を記述 return Observable.Start(() => Debug.Log("例外発生 : " + ex)); }) .Subscribe() .AddTo(this); } }
ここで、Catch()
オペレータの戻り値はIObservable
つまり例外を補足したらその例外を処理して、代わりのObservableを渡すことができます。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .ObserveOnMainThread() .Catch((Exception ex) => { // 60フレーム後にログ出力して後続処理を流す return Observable.TimerFrame(60) .Do(x => Debug.Log("例外発生 : " + ex)) .AsUnitObservable(); }) .Subscribe(_ => { }, () => Debug.Log("On Completed")) .AddTo(this); } }
これにより例えば例外補足時にダイアログを出して、
そのあとの処理を行うObservableを後続に渡すといったことができます。
またCatch()
オペレータには指定した型の例外のみを補足するという便利な機能があります。
using System; using UniRx; using UnityEngine; public class Exception01 : Exception { } public class Exception02 : Exception { } public class Example : MonoBehaviour { private void Start() { Observable .Start(() => { // Exception01かException02のどちらかを発生させる var rand = new System.Random(); if (rand.Next() % 2 == 0) throw new Exception01(); else throw new Exception02(); }) .Catch((Exception01 ex) => Observable.Start(() => Debug.Log("Exception01"))) // Exception01を補足 .Catch((Exception02 ex) => Observable.Start(() => Debug.Log("Exception02"))) // Exception02を補足 .Catch((Exception ex) => Observable.Start(() => Debug.Log("Exception"))) // それ以外の例外はここで補足 .Subscribe() .AddTo(this); } }
Catch時に後続処理が必要ない場合にはCatchIgnoreを使う
前節のように、Catch
オペレータではIObservableを返すことで後続処理を行うストリームを定義できました。
このような後続処理が必要ない場合にはCatch
オペレータの戻り値にEmptyなストリームを渡します。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable .Start(() => throw new Exception()) .Catch((Exception ex) => { Debug.Log("Exception01"); // すぐにこのストリームが完了(Complete)する return Observable.ReturnUnit(); }) .Subscribe(_ => { }, () => Debug.Log("Complete")) .AddTo(this); } }
ただこれは若干冗長です。
CatchIgnore
オペレータを使うと上記と同等の処理が以下のように書けます。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable .Start(() => throw new Exception()) .CatchIgnore((Exception ex) => Debug.Log("Exception01")) .Subscribe(_ => { }, () => Debug.Log("Complete")) .AddTo(this); } }
エラーが起きた時に購読しなおすRetryオペレータ
Retry()
オペレータを使うと、エラーが起こった時にストリームを再購読してくれます。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable .Start(() => throw new Exception()) .DoOnError(ex => Debug.Log(ex)) .Retry(3) .Subscribe(_ => { }, () => Debug.Log("Complete")) .AddTo(this); } }
引数にはリトライ数を渡せます。
特定の例外のみRetryするにはOnErrorRetryを使う
前節のRetry()
はどんな例外が発生しても動作してしまいます。
「この例外が発生したときのみリトライする」という処理を実装したい場合にはOnErrorRetry()
を使います。
using System; using UniRx; using UnityEngine; public class Exception01 : Exception { } public class Exception02 : Exception { } public class Example : MonoBehaviour { private void Start() { Observable .Start(() => { // Exception01かException02のどちらかを発生させる var rand = new System.Random(); if (rand.Next() % 2 == 0) throw new Exception01(); else throw new Exception02(); }) // Exception01だったら繰り返す .OnErrorRetry((Exception01 ex) => Debug.Log("Exception01")) // Exception01だったらComplete .CatchIgnore((Exception02 ex) => Debug.Log("Exception02")) .Subscribe() .AddTo(this); } }
例外発生時に処理はするけど補足はしないDoOnError
これまで紹介したオペレータは例外を補足するため、補足後にはOnError
が発生しなくなります。
例外発生時に何かしらの処理を行いたいけど補足する必要がない場合にはDoOnError()
オペレータを使います。
using System; using UniRx; using UnityEngine; public class Example : MonoBehaviour { private void Start() { Observable.Start(() => throw new Exception()) .ObserveOnMainThread() .DoOnError(ex => Debug.Log("例外発生 : " + ex)) .Subscribe() .AddTo(this); } }
余談ですが、関連するオペレータにDoOnTerminate()
やFinish()
があります。
これらもOnError時に処理を行えるオペレータになります。
詳細は以下の記事にまとめていますので、必要に応じて参照してください。