ニートが学ぶUniRx コルーチンとの連携

はいどーもこんばんは、本日もUniRx日和ですねニートです。
今日のお題はコルーチンとの連携です。
ですが「ニートが学ぶUniRx 入門編その5」でも書いた通り、コルーチンは投げ捨てろと言われています。
なぜ今コルーチンを学ぶのか?
それは知識として「古い技術を知らないと新しい技術の利点がわかりにくいから」です。
ちゅーわけで以下の記事をもとに進めていきますよ。
https://qiita.com/toRisouP/items/c4b9c5701dd6c991b481

コルーチンと合わせると何が嬉しいのか

何が嬉しいかについては、ズバリ上記の記事で以下のように書かれています。
「UniRx単体では宣言的に記述したストリームはif分岐ができなかったり、ストリームの結果を利用してそのまま手続き的な処理に繋げるといった記述が困難でした。ですが、コルーチンとUniRxを併用することでこれらの問題を解決することができるようになります」
確かに僕もストリームを「条件分岐させてえなあ」とか感じることはありました。
おまけにストリームの結果→手続き的な処理へ移行できるとも書いてあります。
順番に見ていきます。

コルーチンからIObservableに変換する

最初は「コルーチンからIObservableに変換する」です。
コルーチンの結果を使ってチェーンを形成できるようです。

まず適当にコルーチンを作ります。

次はこのコルーチンをストリームに変換します。

Observable.FromCoroutineは第一引数にコルーチンを受け取るようです。
publishEveryYieldは「 yieldしたタイミングでOnNextを発行するか? (falseでOnCompletedの直前に1度だけ発行,default = false」とのことです。
実行して試してみます。

Coroutine started.
//ここで3秒待機してから
Coroutine finished.
OnNext
OnComplted

こんな感じになりました。
Observable.FromCoroutineの「publishEveryYield: false」は「コルーチンの終了時にOnNextが飛ぶ」「それからOnCompletedが呼ばれる」ということみたいですね。

yield returnとの合わせ技

コルーチンと言えば、やはりyield returnです。
今度はこれと合わせてストリームを作成してみたいと思います。

まずはコルーチンをドン。

このyield returnで返された値を取り出し、ストリームとして利用することができます。
今回の場合だと0から29の数字ですね。
というわけでストリームを作ってみます。

TestCoroutineから返された0から29の値をストリームに流し、偶数のみを取得したあとに「xです」という文字列に変換しています。
最終的にはデバッグログに出力していますね。

コルーチン内部でOnNextを直接発行する

お次はコルーチンの内部でOnNextを呼んでみたいと思います。
これができると何が嬉しいのかと言えば「内部実装は手続き的な非同期処理で書きつつ、外部からストリームとして扱う」点のようです。

例のごとくコルーチンからドン

コルーチンが引数でobserverを受け取っていますね。
トグルがONならば、OnNextを呼んでランダムな数字を通知するようにしています。
OFFの場合は単に無視されてyield returnに進みます。

ストリームはこんな感じ。

ほぼ見たまんまですね。
こんな使い方もできるんだなあと勉強になります。

マイクロコルーチン

これは単に「yield return null」のみのコルーチンに使えるコルーチンのようです。

IObservableからコルーチンに変換する

今度は逆にストリームをコルーチンに変換します。
これができると何が嬉しいかと言えば、コルーチンの中でストリームの実行結果を待ち受けることができるようになります。
すなわち、yield returnでストリームが流れてくるのを待つことができるようになります。

とりあえずコルーチンのコードをドン。

注目したいのは「ToYieldInstruction」です。
これは「OnCompletedメッセージを受けてyield returnを終了」するようです。
逆に言えば、OnCompletedメッセージが発行されないストリームだと永遠に終了しないことになりますね。
なのでここではFirstOrDefaultが使われています(条件を満たすとOnNextとOnCompletedを両方発行する)。

なぜToYieldInstructionを使うとyield returnで待機できるようになるのか?
戻り値を確認してみると、ObservableYieldInstruction&ltT>となっていました。
このクラスを見てみるとIEnumerator&ltT>を実装しています。
ちゅーわけでyeild returnにも使えるんですね。
IObservableをコルーチンに変換すると言うより、yield returnできる形式に変換する、と考えたほうがわかりやすかったです。

ここまで完成したら、あとは普通にコルーチンを呼ぶだけです。

なお、ToYieldInstructionの戻り値を利用すれば、「ストリーム中で発行されたOnNextメッセージ」を簡単に取得することもできるようです。

コルーチンを直列に実行して待ち受ける

これはとりすーぷさんのサンプルコードを見てほしいのですが、SelectManyで合成すると以下のような実行順になるようです。

1.コルーチンAが実行される
2.コルーチンAが終了する(コルーチンBは待機)
3.コルーチンBが実行される
4.コルーチンBが終了する
5.全てのコルーチン終了後、ObserverのOnNextが実行される

複数コルーチンを同時に起動して結果を待ち受ける

これもとりすーぷさんの記事のサンプルコードを見てほしいです。
Observable.WhenAllは「各コルーチンのObserverがOnNextを通知しても即座にはストリームに流さず、全てのコルーチンでOnCompletedが通知されるまで待機」って感じですかね。

終わりに

別スレッドで~というものは飛ばしましたが、「ニートが学ぶUniRx 入門編その5」のときよりはだいぶん丁寧に読み進められたのではないかなと思います。
今回の記事で最も大切な点は「ストリームとコルーチンは相互に変換することができる」ということですね。
これは他人のコードを読む上でも役に立ちそうなので、きっちりと覚えておこうと思いますよ。

ほなそんな感じでまた。

フォローする