[UniRx]ObserveOnとSubscribeOn

UnityのAPIはメインスレッド上でしか扱えないという制限があります。
しかし、重い処理やネットワークを通した処理などは別のスレッドで実行したほうがよいこともあります。
そこでUniRxの`ObserveOn`と`SubscribeOn`を使用すれば、簡単に処理を実行するスレッドを変更することができます。

# ObserveOn
`ObserveOn`を使用すると`observer.OnNext`や`observer.OnCompleted`などの操作が指定したスケジューラ上で実行されるようになります。
これを使えば`Select`や`Subscribe`に渡す関数を特定のスレッド上で動かすことができます。
`ObserveOnMainThread`を使用すればメインスレッドで動かすことができます。

## 使用例
```csharp
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`を使用する。)

## 使用例
```csharp
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`に渡した関数を実行しています。

# 失敗例
自分が試行錯誤したときのコードです。

```csharp:スケジューラの初期化がメインスレッド以外で起こってしまう
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();
}
```

```csharp:MainThreadDispatcherが動き出すより前にOnNextしてしまう
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);
}
```

```csharp:SubscribeOnMainThreadしてもSubscribeに渡した関数はメインスレッドで実行されるわけではない
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));
}
```

# 最後に
どうしてもわからない部分はソースを読みましょう。
内部の処理を知ることでどのように書くとよいかがなんとなく見えてくると思います。