はじめに
UniRxを利用していたときはIObservable.ToUniTask
を活用したコードをよく書いてました。
// OnNextがされるまで待つ var result = await subject.ToUniTask(useFirstValue: true, cancellationToken: source.Token); // OnCompletedがされるまで待つ var result2 = await subject.ToUniTask(useFirstValue: false, cancellationToken: source.Token);
【UniRx,UniTask】IObservable.ToUniTaskメソッドをうまく使いこなそう - はなちるのマイノート
ただR3の場合はIObservable
を実装していないのでこれを活用することはできません。
github.com
どうやって実現しようかなと調べていたところやり方を見つけたので書き残しておこうと思います。
概要
以下のIssueで言及されていて、FirstAsync
・LastAsync
を使うと実現できるようです。
In R3, FirstAsync or LastAsync allows you to decide which way to wait.
Usually in conver to Task, the code expects Last, but First is often more useful.
It is natural to want to convert R3 to an awaitable type (that is why FirstAsync and LastAsync exist).
It is true that there is an overhead due to not being able to convert directly, but we do not think it is large enough to be a problem in situations where this is required.
// DeepL翻訳
R3では、FirstAsyncとLastAsyncのどちらで待つかを決めることができる。
通常、Taskに変換する場合、コードではLastを期待するが、Firstの方が便利なことが多い。
R3を待ち型に変換したいと思うのは自然なことだ(だからFirstAsyncとLastAsyncが存在する)。
直接変換できないことによるオーバーヘッドがあるのは事実だが、変換が必要な状況で問題になるほど大きくないと考える。
やり方
// FirstAsyncを活用するとOnNextまで待つ (ToUniTaskのuseFirstValue=true) var result1 = await subject.FirstAsync(destroyCancellationToken); // LastAsyncを活用するとOnComplatedまで待つ (ToUniTaskのuseFirstValue=false) var result2 = await subject.LastAsync(destroyCancellationToken);
重要な内部実装は以下でして、FirstAsync(FirstLastSingleOperation.First)のときはOnNextで値を返し、LastAsnyc(FirstLastSingleOperation.Last)のときは最後のOnNextの値をOnComplatedで返してますね。
github.com
実験コード
private async UniTask Start() { var subject = new Subject<int>(); subject.Subscribe(x => Debug.Log(x)) .AddTo(this); var task = UniTask.Create(async () => { // 最初のOnNextを待つ var result1 = await subject.FirstAsync(destroyCancellationToken); Debug.Log($"{result1} in task"); // OnCompletedまで待つ(最後にOnNextされた値を取得) var result2 = await subject.LastAsync(destroyCancellationToken); Debug.Log($"{result2} in task"); }); subject.OnNext(1); await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: destroyCancellationToken); subject.OnNext(2); await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: destroyCancellationToken); subject.OnNext(3); await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: destroyCancellationToken); subject.OnCompleted(); await task; subject.Dispose(); }