きゅぶろぐ

きゅぶんずの ぶろぐができて べんりだな

UniTask v2のRCが出たので触ってみた (RC4対応版)

雑談

Unityでもasync/awaitが当たり前のように使われるようになったこの2020年、
コルーチンはもちろん、Taskが無かったからRxで補っていたところも、
全部UniTaskで置き換えればよくね・・・?と思っているわけです。
そんなところに、UniTask v2の話が流れてきたので触ってみました。

LINQでawait出来る

var hoge = new[] { 1, 2, 3 };
var fuga = await hoge.Select(async x =>
{
    await UniTask.Delay(1000);
    return x + 1;
});

一番待ち望んでいた!!というか、一番使いみちが分かりやすいのがこれ!
Selectで待てる、もちろんMaxでもなんでも待てる。
今まで、ああ〜ここLINQで書いてたのに非同期混ざったからforeachで展開しよ〜ってやってたのがそのまま通るようになって嬉しい!

UniTask.Createが追加

UniTask.Create(async () =>
{
    await UniTask.Delay(1000);
    Debug.Log("1秒経過");
}).Forget();

(なんで無かったんだろう)

ForEachAwaitAsync

var hoge = new[] { 1, 2, 3 };
await hoge.ToUniTaskAsyncEnumerable().Select(async x =>
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    return x + 1;
}).ForEachAwaitAsync(async x =>
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log($"Output {x}");
});

配列を、時間のかかる処理で回しながら、時間のかかる表示を行う、みたいなことが簡単に出来る。

UniTaskAsyncEnumerable.EveryUpdate

UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(x =>
{
    Debug.Log("Update");
}).Forget();

毎フレーム飛んでくるので、これは毎フレームUpdateと表示される。

UniTaskAsyncEnumerable.EveryUpdate().ForEachAwaitAsync(async x =>
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log("Update");
}).Forget();

ForEachAwaitAsyncの中のTaskを処理している最中は次の値が来ることが無い。
つまり、このコードでは1秒に1回"Update"と表示される。
何が嬉しいかは次の OnClickAsAsyncEnumerable で。

OnClickAsAsyncEnumerable

UnityEngine.UI.Button button;
button.OnClickAsAsyncEnumerable().ForEachAwaitAsync(async x =>
{
    Debug.Log("ボタン押した処理開始!");
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log("ボタン押した処理終了!");
});

こうしておくと、ボタンをどれだけ連打しても処理が多重に走ることが無い。嬉しい。

AsyncReactiveProperty (RC2挙動変更)

var hp = new AsyncReactiveProperty<int>(10);
Debug.Log("Set hp = 10");

hp.ForEachAwaitAsync(async x =>
{
    Debug.Log($"Start hp = {x}");
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log($"Finish hp = {x}");
}).Forget();

await UniTask.Delay(TimeSpan.FromSeconds(0.3));
Debug.Log("Set hp = 20");
hp.Value = 20;

await UniTask.Delay(TimeSpan.FromSeconds(0.8));
Debug.Log("Set hp = 30");
hp.Value = 30;

// Output
/*
0.0 | Set hp = 10
0.0 | Start hp = 10
0.3 | Set hp = 20
1.0 | Finish hp = 10
1.1 | Set hp = 30
1.1 | Start hp = 30
2.1 | Finish hp = 30
*/

ReactivePropertyから想像されるように、現在の値がまず流れてくる。
これも、ForEachAwaitAsyncで処理中の時に流れてきた値は流れてこないので注意。
(このサンプルでは20の値が処理されない。)

Queue (RC2追加)

ForEachAwaitAsyncで処理を回してるんだけど、
全ての値を処理したい!というケースはQueueを挟みましょう。

var hp = new AsyncReactiveProperty<int>(10);
Debug.Log("Set hp = 10");

// ↓ さっきのコードのここにQueue挟んだだけ
hp.Queue().ForEachAwaitAsync(async x =>
{
    Debug.Log($"Start hp = {x}");
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log($"Finish hp = {x}");
}).Forget();

await UniTask.Delay(TimeSpan.FromSeconds(0.3));
Debug.Log("Set hp = 20");
hp.Value = 20;

await UniTask.Delay(TimeSpan.FromSeconds(0.8));
Debug.Log("Set hp = 30");
hp.Value = 30;

// Output
/*
0.0 | Set hp = 10
0.0 | Start hp = 10
0.3 | Set hp = 20
1.0 | Finish hp = 10
1.0 | Start hp = 20
1.1 | Set hp = 30
2.0 | Finish hp = 20
2.0 | Start hp = 30
3.0 | Finish hp = 30
*/

ForEachAwaitAsync するつもりで ForEachAsync しないように! (RC4で対応済)

await hoge.ToUniTaskAsyncEnumerable().ForEachAwaitAsync(async x =>
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log($"Output {x}");
});

このコードは、1秒後から、hogeの内容が1秒毎に出てくる。

間違えて ForEachAsync にしちゃった!

await hoge.ToUniTaskAsyncEnumerable().ForEachAsync(async x =>
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log($"Output {x}");
});

これでもビルドは通ってしまう。悲しい。

RC4でビルドエラーになるようになりました。
うっかりActionにTask渡しちゃうやつってビルドエラーに出来たのか・・・勉強になります。

まとめ

今のところUniRx, UniTask(v1)でコードを書いていますが
UniTask v2はv1以上に便利で習得必須になりそうなので、リリースされるのを楽しみにしています!!

github.com