UnityのAPIはメインスレッド上でしか扱えないという制限があります。
しかし、重い処理やネットワークを通した処理などは別のスレッドで実行したほうがよいこともあります。
そこでUniRxのObserveOn
とSubscribeOn
を使用すれば、簡単に処理を実行するスレッドを変更することができます。
ObserveOn
ObserveOn
を使用するとobserver.OnNext
やobserver.OnCompleted
などの操作が指定したスケジューラ上で実行されるようになります。
これを使えばSelect
やSubscribe
に渡す関数を特定のスレッド上で動かすことができます。
ObserveOnMainThread
を使用すればメインスレッドで動かすことができます。
使用例
void Start()
{
// Scheduler.MainThreadにメインスレッドでアクセスしておく必要がある(初めてアクセスしたときに内部の変数が初期化されるため)
var s = Scheduler.MainThread;
new Thread(() =>
{
// Debug.Log(this.gameObject.name); // get_gameObject can only be called from the main thread.
Observable.Create<string>(observer =>
{
observer.OnNext("");
return null;
})
.ObserveOnMainThread()
.Select(_ => this.gameObject.name)
.Subscribe(Debug.Log);
}).Start();
}
注:Thread
をそのまま使用しているのはサンプルだからです。普段はObsavable.Start
とかを使用したほうがいいです。
ObserveOnMainThread
を使用した後のSelect
やSubscribe
に渡した関数はメインスレッドで実行されるためthis.gameObject.name
にアクセスしてもエラーになりません。
SubscribeOn
SubscribeOn
を使用するとSubscribe
が指定したスケジューラ上で実行されるようになります。
内部のSubscribe
がスケジューラ上で実行されるようになるだけでSubscribe
に渡した関数がスケジューラ上で実行されるわけではないことに注意が必要です。
SubscribeOnMainThread
を途中に挟んでもSubscribe
に渡した関数はメインスレッドで実行されないこともあります。
(基本的にはIObserver.OnNext
を実行したスレッド上でSubscribe
に渡した関数が実行される。変更したい場合はObserveOn
を使用する。)
使用例
void Start()
{
Observable.Create<string>(observer =>
{
// 重い処理, メインスレッド上で実行するとゲームが止まってしまう
Thread.Sleep(3000);
observer.OnNext("");
return null;
})
// Subscribeをスレッドプール上で実行する
.SubscribeOn(Scheduler.ThreadPool)
// UnityAPIを使用するためにメインスレッドに切り替える
.ObserveOnMainThread()
.Select(_ => this.gameObject.name)
.Subscribe(Debug.Log);
}
Obsavable.Create
に渡した関数内ではThread.Sleep(3000)
を実行しており、メインスレッドで実行するとゲームが止まってしまいます。
SubscribeOn(Scheduler.ThreadPool)
を使用することでスレッドプール上でObsavable.Create
に渡した関数を実行しています。
失敗例
自分が試行錯誤したときのコードです。
void Start()
{
var subject = new Subject<int>();
new Thread(() =>
{
subject
// Exception: UniRx requires a MainThreadDispatcher component created on the main thread. Make sure it is added to the scene before calling UniRx from a worker thread.
.ObserveOnMainThread()
.Subscribe(_ => Debug.Log(this.gameObject.name));
}).Start();
}
void Start()
{
var subject = new Subject<int>();
subject
.SubscribeOnMainThread()
.Subscribe(_ => Debug.Log("hoge"));
// OnNextしてもDebug.Log("hoge")が実行されない
// SubscribeOnMainThread()するとMainThreadDispatcher.Updateが実行されるまではSubscribeされないため
// 1フレーム待つと正常に動く
subject.OnNext(0);
}
void Start()
{
IObserver<Unit> o = null;
Observable.Create<Unit>(observer =>
{
o = observer;
return null;
})
.SubscribeOnMainThread()
// get_gameObject can only be called from the main thread.
.Subscribe(_ => Debug.Log(this.gameObject.name));
this.UpdateAsObservable()
.Where(_ => o != null)
.ObserveOn(Scheduler.ThreadPool)
.Subscribe(_ => o.OnNext(Unit.Default));
}
最後に
どうしてもわからない部分はソースを読みましょう。
内部の処理を知ることでどのように書くとよいかがなんとなく見えてくると思います。