UniTaskの使い方2020 / UniTask2020
by
torisoup
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
UniTaskの使い方2020 2020/6/26 とりすーぷ
Slide 2
Slide 2 text
自己紹介 • とりすーぷ(@toRisouP) • UnityでxR系の開発してます • 最近はVRChatで遊んでます • Microsoft MVP • Developer Technologies2018~
Slide 3
Slide 3 text
今回の主な内容 • 非同期処理、async/awaitの解説 • UniTaskについて
Slide 4
Slide 4 text
想定バージョン • Unity 2019.x系 • UniTask 2.0.21
Slide 5
Slide 5 text
以前解説した資料 • UniTaskについて説明した資料 • UniTask入門 • https://learning.unity3d.jp/2974/ • UniTask2が出ていろいろ変わった部分がある • その部分も含めてアップデートしてます
Slide 6
Slide 6 text
もくじ • 「非同期処理」 • 「async/await」 • 「UniTask」 • 「UniTaskAsyncEnumerable」
Slide 7
Slide 7 text
非同期処理 #とは
Slide 8
Slide 8 text
非同期処理とは • 非同期処理
Slide 9
Slide 9 text
非同期処理とは • 非同期処理
Slide 10
Slide 10 text
非同期処理とは • 非同期処理 • そもそも「同期処理」って何?
Slide 11
Slide 11 text
同期処理 • 処理(関数)の実行が終わるまでちゃんと待つもの • 呼び出した処理が終わるまで自分を一時停止する
Slide 12
Slide 12 text
非同期処理 • 処理が終わるのを待たず先に元の処理に戻ってくるもの • 「処理の呼び出し終了」と「処理の完了」のタイミングが一致しないもの • 裏で別の処理を並行に実行しながら、元の処理を継続できる
Slide 13
Slide 13 text
非同期処理
Slide 14
Slide 14 text
よくある勘違い • 非同期処理 = マルチスレッド処理
Slide 15
Slide 15 text
よくある勘違い • 非同期処理 = マルチスレッド処理 • これは間違い
Slide 16
Slide 16 text
よくある勘違い • 非同期処理 = マルチスレッド処理 • これは間違い • 非同期処理は「完了を待たずに呼び出し元にもどる処理」のこと • スレッドがどうこうとかいう話は本来は関係ない • マルチスレッド処理と非同期処理の相性がいいのは事実だけど、概念としては別物 • なんならシングルスレッドのみを使っても非同期処理は実現できる
Slide 17
Slide 17 text
例:コルーチン • コルーチン • Unityのメインスレッドを使った非同期処理の機構 • メインスレッドのみを使って非同期処理を書くこともできる
Slide 18
Slide 18 text
メインスレッドのみを使ったコルーチン
Slide 19
Slide 19 text
メインスレッドのみを使ったコルーチン 「前に進む」という非同期処理を実行し、 それが完了したらコールバックが実行される
Slide 20
Slide 20 text
例:イベント処理 • 「イベント処理」は非同期処理の仲間 • 「同じ非同期処理を何度も待ち続ける」ともみなすことができる • 「いつ完了するかわからない処理」を「何度も繰り返して待つ」
Slide 21
Slide 21 text
イベント処理は非同期処理 • OnCollisionEnterイベント • ぶつかるたびにこの関数が実行される
Slide 22
Slide 22 text
イベント処理は非同期処理 • 「ぶつかるまで待つ」という非同期処理を 「繰り返し待ち続ける」とみなせる • async/awaitのコードに書き直しても動く(要:UniTask)
Slide 23
Slide 23 text
非同期処理 まとめ • 非同期処理とマルチスレッド処理は別の概念 • 「Unityで非同期処理なんて使えないでしょ」は誤解 • メインスレッドだけでも非同期処理は書ける • Unity開発でも非同期処理は使ってるはず • 「コルーチン」はまさに非同期処理の仕組みそのもの • 「イベント処理」も非同期処理の仲間
Slide 24
Slide 24 text
async/await async/awaitを使ったことない人向けに雑にせつめい
Slide 25
Slide 25 text
async/await • 「async」と「await」というキーワードを使った記法 • C# 5.0で追加された機能 • 非同期処理を同期処理と似た記法で扱うことができる • asyncってメソッドの頭につけると非同期メソッド化する • 非同期メソッド内でawaitって書くとそこで処理が一時停止する • 裏でコンパイラがステートマシンを作っていい感じにしてくれる
Slide 26
Slide 26 text
async/awaitの使い方 • Task(またはそれに準ずるオブジェクト) と組み合わせて使う • 今の御時世ではValueTask, UniTaskの方がよい
Slide 27
Slide 27 text
「待つ」 • async/awaitは非同期処理を「待つ」ための機構 • 並行で実行した処理が終わるのをいい感じに待てる! • 簡単な記法で「待つ」ことができる! • 非同期処理から結果を取り出すのも簡単! • try-catchも使えてエラーハンドリングも完璧!
Slide 28
Slide 28 text
注意 • async/awaitはマルチスレッド処理のための機能ではない • awaitするとマルチスレッド処理になる、は間違い! • async/awaitは「待つ」ための仕組みであって、スレッドの話は関係ない! • マルチスレッドの機能がくっついてるのは「Task」の方 • async/awaitはTaskと組み合わせることが多いので、それで誤解しやすい • だいたいTaskが悪い
Slide 29
Slide 29 text
「async/await」をわかりやすく言うと • 強い“コルーチン” • いろいろ語弊はあるがUnityからC#を始めた人にはこういうイメージが 一番伝わりやすいと思う
Slide 30
Slide 30 text
比較 • 記法はすごく似てる • とりあえず最初はコルーチンの感覚でasync/awaitを書けば良い コルーチン async/await
Slide 31
Slide 31 text
async/awaitの書き方解説 asyncって書くと、 awaitが使えるようになる
Slide 32
Slide 32 text
async/awaitの書き方解説 コルーチンと違って戻り値が指定できる! await時にそのまま受け取り可能! ジェネリック版のTaskなどを指定して そのままreturnするだけ
Slide 33
Slide 33 text
async/awaitの書き方解説 キャンセルにはCancellationTokenを使う
Slide 34
Slide 34 text
コルーチンと比較したasync/await コルーチン async/await どこで動くか Unityのライフサイクル上で実行 (GameObjectに紐づく) 実装による 戻り値 利用できない returnが使える キャンセル GameObjectを破棄すれば止まる 明示的に停止する必要あり 「待てる」対象 限定的 (AsynOperation,YieldConstruction など) 何でも待てる (GetAwaiter()さえあれば) エラーハンドリング つらい try-catchが使える 非同期処理の連結 かなりつらい 手続き的に書ける
Slide 35
Slide 35 text
「async/await」まとめ • async/awaitは「待つ」ための機構 • 非同期処理を気軽に扱うための機構 • async/awaitはマルチスレッド処理の機能ではない!!!!! • Unityでもasync/awaitは便利に使える • めっちゃ進化したすごい「コルーチン」と思えばOK • Unity上でも普通に使えるし、UniTaskを使えばもっと便利になる
Slide 36
Slide 36 text
ちゃんとした解説 • ちゃんとしたasync/awaitの解説はこちら • さては非同期だなオメー!async/await完全に理解しよう • https://learning.unity3d.jp/275/ • async/await のしくみ • https://learning.unity3d.jp/1510/
Slide 37
Slide 37 text
Unityでasync/awaitを快適に使うために •「UniTask」 • Unity上でasync/awaitを快適に不便なく使うためのライブラリ
Slide 38
Slide 38 text
UniTask
Slide 39
Slide 39 text
UniTaskとは • Unity向けのasync/await拡張ライブラリ • Unityにおけるasync/awaitを大幅に強化してくれる • MITライセンスで公開中 • https://github.com/Cysharp/UniTask • Releaseからパッケージを入れるなり • OpenUPMなどからいれるなり
Slide 40
Slide 40 text
UniTaskの機能 • Unityにおけるasync/awaitを大幅強化 • async/awaitのパフォーマンスの向上(アロケーションが徹底的に削減) • Unity開発に特化した機能の提供 • 各種Awaiter実装の提供 • UnityEditor上でawait状態の可視化(UniTaskTracker) • UniTaskAsyncEnumerable / UniTask.LINQ
Slide 41
Slide 41 text
UniTask 機能紹介
Slide 42
Slide 42 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 43
Slide 43 text
UniTask/UniTask いちばん基本的なオブジェクト
Slide 44
Slide 44 text
UniTask / UniTask • ValueTask / IValueTaskSource のUnity向け実装 • ゼロアロケーションで動作する • 理由が無い限りTaskはもう使わない • 代わりにUniTaskを使おう
Slide 45
Slide 45 text
TaskとUniTaskの違い Task UniTask 機能 Unityでは不要な機能が多い Unityで活用できる機能中心 オブジェクトサイズ 大きい 小さい 実行コンテキスト SynchronizationContextに 暗黙的に依存 明示的に操作しない限り 現在のコンテキストを保つ メモリアロケート 常にヒープを確保する ゼロアロケーション await時の制約 なし 同じUniTaskインスタンスは 2回awaitできない
Slide 46
Slide 46 text
TaskとUniTaskの違い Task UniTask 機能 Unityでは不要な機能が多い Unityで活用できる機能中心 オブジェクトサイズ 大きい 小さい 実行コンテキスト SynchronizationContextに 暗黙的に依存 明示的に操作しない限り 現在のコンテキストを保つ メモリアロケート 常にヒープを確保する ゼロアロケーション await時の制約 なし 同じUniTaskインスタンスは 2回awaitできない
Slide 47
Slide 47 text
TaskとUniTaskの違い Task UniTask 機能 Unityでは不要な機能が多い Unityで活用できる機能中心 オブジェクトサイズ 大きい 小さい 実行コンテキスト SynchronizationContextに 暗黙的に依存 明示的に操作しない限り 現在のコンテキストを保つ メモリアロケート 常にヒープを確保する ゼロアロケーション await時の制約 なし 同じUniTaskインスタンスは 2回awaitできない
Slide 48
Slide 48 text
制約 • 同じUniTaskインスタンスは2回以上awaitできない • パフォーマンス向上のための仕様 • 2回awaitすると例外が出る • 2回以上awaitしたい場合は • Preserve()メソッドを呼ぶ • UniTask.Lazy()で包む
Slide 49
Slide 49 text
UniTaskVoid • async void の代わりに使うやつ • UniTaskの機構の上で動くasync void相当のやつ
Slide 50
Slide 50 text
UniTask/UniTaskの使い方
Slide 51
Slide 51 text
Taskから置換すれば動く • Taskって書いてるところをUniTaskに変えれば動く • ちょっと書き換えるだけでパフォーマンスが向上する!
Slide 52
Slide 52 text
UniTaskのキャンセル方法 • 基本はCancellationTokenを使う • CancellationTokenを使ってキャンセル命令を送ることができる • CancellationTokenは省略せずに可能な限り渡すことが大事 • 「面倒くさいから省略しちゃおう」は絶対ダメ
Slide 53
Slide 53 text
GetCancellationTokenOnDestroyが便利 • MonoBehaviour上ならこれが楽 • Destroyされたら自動的にキャンセルされるCancellationTokenを生成する
Slide 54
Slide 54 text
CancellationTokenSource • CancellationTokenSourceを使うと手動で作れる
Slide 55
Slide 55 text
CancellationTokenが渡せない時 • OperationCanceledExceptionを使う • CancellationTokenが相手に渡せないときはこの例外を投げればOK
Slide 56
Slide 56 text
補足: OperationCanceledException • UniTaskをキャンセル状態にすることができる特殊例外 • この例外が発行されたときのみUniTaskはキャンセル状態になる • それ以外の例外の場合は失敗状態になる
Slide 57
Slide 57 text
補足: UniTaskScheduler • UniTaskの未処理例外の管理を行っている機構 • これはUniTask内で発生した例外のうちOperationCanceledException のみ無視してくれる(ログに出さずにもみ消してくれる) • UniTaskを使うときはOperationCanceledExceptionはcatchせず スルーしておけばOK
Slide 58
Slide 58 text
例
Slide 59
Slide 59 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 60
Slide 60 text
UniTaskCompletionSource UniTaskを手動でつくる仕組み
Slide 61
Slide 61 text
UniTaskCompletionSource • 任意のタイミングで状態を変更できるUniTaskを作る機能 • UniRx.AsyncSubjectと似てる • 生成されたUniTaskは何回でもawaitできる
Slide 62
Slide 62 text
使用例:初期化完了待ち
Slide 63
Slide 63 text
使用例:初期化完了待ち
Slide 64
Slide 64 text
派生:AutoResetUniTaskCompletionSource • 内部的にリソースが再利用されるUTCS • 使い方はUniTaskCompletationSourceとほぼ同じ • インスタンス化の方法がちょっと違う • ただしこっちは1回しかawaitできない • ライブラリ内でオブジェクトプールされてる • デストラクト時に自動的に返却される • 使い捨て用途向き
Slide 65
Slide 65 text
比較 • UniTaskCompletionSource • 何回でもawaitできるUniTaskを生成できる • フィールドに定義して使ったりするのによさげ • AutoResetUniTaskCompletionSource • 自動的に再利用されるため使用コストが低い • 生成されたUniTaskは1回しかawaitできない • メソッド内など、狭いスコープ内で使い捨てる用途に向いてる
Slide 66
Slide 66 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 67
Slide 67 text
Awaiter awaitするときに使うやつ
Slide 68
Slide 68 text
Awaiter • オブジェクトをawait可能にする機構 • あるオブジェクトのGetAwaiter()メソッドからAwaiterが取得できるとき、 そのオブジェクトはawaitが可能になる • UniTaskを導入すると、いろんなオブジェクトにこのAwaiterが追加される • つまり、Unityのあらゆるオブジェクトや事象がawaitできるようになる!
Slide 69
Slide 69 text
Awaiter: AsyncOperation • コルーチンで待たないといけなかったやつがasync/awaitで待機可能になる
Slide 70
Slide 70 text
Awaiter: AsyncOperation • returnでそのまま結果を返せて便利!
Slide 71
Slide 71 text
Awaiter:コルーチン
Slide 72
Slide 72 text
Awaiter: uGUI • uGUIのイベントもawaitで待てるようになる • awaitが1回でいいなら On***Async()を呼び出す
Slide 73
Slide 73 text
Awaiter: uGUI 繰り返してawaitする場合は「AsyncHandler」を取得する
Slide 74
Slide 74 text
Awaiter: uGUI LINQと組み合わせるならUniTaskAsyncEnumerable化する
Slide 75
Slide 75 text
Awaiter: MonoBehaviour MonoBehaviourのすべてのイベントをawaitできる
Slide 76
Slide 76 text
Awaiter: DOTween OpenUPMからDOTweenを導入、または「UNITASK_DOTWEEN_SUPPORT」 を定義している場合に利用可能
Slide 77
Slide 77 text
Awaiter : CancellationToken
Slide 78
Slide 78 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 79
Slide 79 text
ファクトリメソッド 特殊なUniTaskを生成したりする便利な関数
Slide 80
Slide 80 text
ファクトリメソッド • 特殊な動作をするUniTaskを生成する • 基本はawaitと組み合わせて使う • たくさんある • 全部は紹介しません
Slide 81
Slide 81 text
UniTask.Delay / DelayFrame • 指定した時間/フレーム数待機できる
Slide 82
Slide 82 text
UniTask.Yield • awaitすると実行コンテキストを切り替えることができる • すでに同コンテキストにいる場合はそのまま1回待機する
Slide 83
Slide 83 text
指定可能なタイミング • Initialization • LastInitialization • EarlyUpdate • LastEarlyUpdate • FixedUpdate • LastFixedUpdate • PreUpdate • LastPreUpdate • Update • LastUpdate • PreLateUpdate • LastPreLateUpdate • PostLateUpdate • LastPostLateUpdate
Slide 84
Slide 84 text
指定可能なタイミング • Initialization • LastInitialization • EarlyUpdate • LastEarlyUpdate • FixedUpdate • LastFixedUpdate • PreUpdate • LastPreUpdate • Update • LastUpdate • PreLateUpdate • LastPreLateUpdate • PostLateUpdate • LastPostLateUpdate • コルーチンの WaitForEndOfFrame に相当
Slide 85
Slide 85 text
UniTask.NextFrame • 確実に次のフレーム(以降)のコンテキストに切り替える • Yield()はタイミングによっては同じフレーム間で切り替わることがある • こちらは確実に次のフレーム(以降)となる
Slide 86
Slide 86 text
UniTask.SwitchToThreadPool • awaitするとコンテキストをスレッドプールに切り替える
Slide 87
Slide 87 text
UniTask.SwitchToMainThread • awaitすると実行コンテキストをメインスレッドに戻す • 戻り先は PlayerLoopTiming.Update に相当 • すでにメインスレッドなら何もしない
Slide 88
Slide 88 text
UniTask.WaitUntil / WaitWhile • 条件を満たすまで/満たさなくなるまで待つ • コルーチンにあったやつ
Slide 89
Slide 89 text
UniTask.WaitUntilCanceled • CancellationTokenをUniTask化する • キャンセル命令が出ると、UniTaskは完了状態になる • キャンセル命令が出たらawaitの次に進む、ができる
Slide 90
Slide 90 text
UniTask.WhenAll • 複数のUniTaskがすべて完了するのを待つ
Slide 91
Slide 91 text
UniTask.WhenAll • 型が一致してなくてもOK
Slide 92
Slide 92 text
UniTask.WhenAll • ValueTupleへのawaitはUniTask.WhenAll扱いになる
Slide 93
Slide 93 text
UniTask.WhenAny • 複数のUniTaskのうちどれか1個が終わるのを待つ (ちなみに uniTask.Timeoutの実装はこれとだいたい同じ)
Slide 94
Slide 94 text
UniTask.Create • UniTaskをラムダ式から作る時に便利
Slide 95
Slide 95 text
UniTask.Defer • UniTaskの起動を遅延できる • awaitするまでUniTaskの起動を遅らせることができる
Slide 96
Slide 96 text
UniTask.Lazy • UniTaskの起動を遅延できる • こっちはAsyncLazy型になる(何回でもawaitできるUniTaskになる)
Slide 97
Slide 97 text
それぞれの違い • UniTask.Create • Createを呼んだ瞬間に新しいUniTaskを生成する • UniTask.Defer • awaitした瞬間にUniTaskを1回だけ生成する • 1回しかawaitできないかわりにLazyより軽量 • UniTask.Lazy • awaitした瞬間にAsyncLazy型にラップしてUniTaskを生成する • 生成したUniTaskは何回でもawaitできる
Slide 98
Slide 98 text
それぞれの違い • UniTask.Create • Createを呼んだ瞬間に新しいUniTaskを生成する • UniTask.Defer • awaitした瞬間にUniTaskを1回だけ生成する • 1回しかawaitできないかわりにLazyより軽量 • UniTask.Lazy • awaitした瞬間にAsyncLazy型にラップしてUniTaskを生成する • 生成したUniTaskは何回でもawaitできる
Slide 99
Slide 99 text
UniTask.Void • async/awaitをvoid型の関数にラップできる • 中に書いたasyncメソッドはForget()される
Slide 100
Slide 100 text
UniTask.Action/UnityAction • async/awaitをAction/UnityActionにラップできる
Slide 101
Slide 101 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 102
Slide 102 text
その他便利メソッド
Slide 103
Slide 103 text
Timeout / TimeoutWithoutException • UniTaskのawaitにタイムアウトをつける • この2つの違いはタイムアウト時に例外が飛ぶ / 飛ばない
Slide 104
Slide 104 text
Forget() • UniTaskを投げっぱなし(Fire-and-forget)化する • 非同期処理を起動するけどawaitせず放置するときに使う
Slide 105
Slide 105 text
IDisposable.AddTo(CancellationToken) • IDisposable.Dispose()呼び出しをCancellationTokenに 連動させることができる
Slide 106
Slide 106 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 107
Slide 107 text
UniTaskTracker
Slide 108
Slide 108 text
UniTaskTracker • Unityエディタ上でUniTaskの待機状況を確認できる • 待機状況、経過時間、スタックトレースを確認できる • リークしてないかのチェックが簡単にできる
Slide 109
Slide 109 text
UniTaskの機能 • UniTask / UniTask • UniTaskCompletionSource • 各種Awaiter • ファクトリメソッド • その他便利なメソッド • UniTaskTracker • UniTaskAsyncEnumerable
Slide 110
Slide 110 text
UniTaskAsyncEnumerable
Slide 111
Slide 111 text
UniTaskAsyncEnumerable • IAsyncEnumerableのUniTask実装 • 複数個の非同期処理をasync/awaitベースで扱える仕組み • C# 8が使えないUnityバージョンでも動作する • 長くなるので一旦区切ります(この後に話す)
Slide 112
Slide 112 text
UniTaskの機能紹介 まとめ • Unityでのasync/await生活を便利にする機能満載 • async/await使うなら無条件で導入するべき • UniRxと比べるとまだわかりやすいライブラリ
Slide 113
Slide 113 text
UniTaskAsyncEnumerable UniTask2の目玉機能
Slide 114
Slide 114 text
UniTaskAsyncEnumerable • IAsyncEnumerableのUniTask版実装
Slide 115
Slide 115 text
UniTaskAsyncEnumerable • IAsyncEnumerableのUniTask版実装 • まずはIAsyncEnumerableを先に理解したほうが話が早い
Slide 116
Slide 116 text
IAsyncEnumerable ここからはUnityではなく、純粋なC#の話
Slide 117
Slide 117 text
IAsyncEnumerable • C#8.0で正式に追加されたPull型非同期ストリーム • IEnumerableの非同期版 • 基本はawait foreachとセットで使う
Slide 118
Slide 118 text
インタフェース定義
Slide 119
Slide 119 text
IEnumerableとの比較
Slide 120
Slide 120 text
IEnumerableとの比較 イテレータを返す 非同期イテレータを返す
Slide 121
Slide 121 text
IEnumeratorの比較
Slide 122
Slide 122 text
IEnumeratorの比較 次の値が存在するかを非同期で確認できる trueなら存在する 次の値が存在するならtrue
Slide 123
Slide 123 text
使用例の比較
Slide 124
Slide 124 text
使用例の比較
Slide 125
Slide 125 text
Foreachの比較
Slide 126
Slide 126 text
Foreachの比較 2箇所でawaitが使える ・次の値を取りに行く時 ・得た値を使って処理を実行するとき
Slide 127
Slide 127 text
何を実現するためのものなのか • 複数個の非同期処理に対して逐次処理を行う機構 • 直列実行が得意
Slide 128
Slide 128 text
「複数個の」非同期処理といえば • IObservableがある • いわゆるReactive Extensions(Rx) • これと何が違うのか?
Slide 129
Slide 129 text
IObservableとの違い • IObservableはPush型 • IAsyncEnumerableはPull型
Slide 130
Slide 130 text
比較
Slide 131
Slide 131 text
使い分け • IObservable • メッセージを多数にブロードキャストするのに向く(イベント駆動向け) • 非同期処理の結果を用いて並行実行するのが得意 • IAsyncEnumerable • 単一の消費者がメッセージの流量を制御しながら処理できる • 非同期処理の逐次実行がやりやすい
Slide 132
Slide 132 text
UniTaskAsyncEnumerable Unityの話にもどる
Slide 133
Slide 133 text
UniTaskAsyncEnumerable • IAsyncEnumerableのUniTask版実装 • インタフェースはIUniTaskAsyncEnumerable • 仕組みはIAsyncEnumerableとまったく同じ
Slide 134
Slide 134 text
IAsyncEnumerable相当のことができる! • C# 8がまだ使えないUnityでもIAEが使える! • await foreach構文が使えない以外は同じことができる • 挙動はまったく同じ • LINQも使える
Slide 135
Slide 135 text
UniTaskAsyncEnumerableの 使い方 await foreachの代替方法
Slide 136
Slide 136 text
await foreachの代わりに • ForEachAsync • ForEachAwaitAsync • Subscribe
Slide 137
Slide 137 text
例:HTTP通信する
Slide 138
Slide 138 text
ForEachAsync • 結果を待ち受けて、同期的に処理を行う • メッセージを使った処理を同期的に実行する • 処理が終わったら次の値を取りに行く
Slide 139
Slide 139 text
ForEachAsync
Slide 140
Slide 140 text
ForEachAwaitAsync • 中でasync/awaitが使えるようになる • asyncメソッドが最後まで終わったら次の値を取りに行く
Slide 141
Slide 141 text
ForEachAwaitAsync
Slide 142
Slide 142 text
Subscribe • async/awaitが使えるが、Forget()される • ForEachAwaitAsyncと記法は似てるけど挙動がぜんぜん違う
Slide 143
Slide 143 text
Subscribe この場合は 「await UniTask.Delay」を 呼び出す意味がない
Slide 144
Slide 144 text
メッセージの消費パターン • ForEachAsync • while (await e.MoveNextAsync()) action(e.Current); • ForEachAwaitAsync • while (await e.MoveNextAsync()) await action(e.Current); • Subscribe • while (await e.MoveNextAsync()) action(e.Current).Forget();
Slide 145
Slide 145 text
UniTask.LINQ IUniTaskAsyncEnumerableの加工ができる
Slide 146
Slide 146 text
UniTask.LINQ • UniTaskAsyncEnumerableでもLINQが使える! • 非同期シーケンスに対していろいろ加工ができる機能 • UniRxのオペレータに近い • 数が多いのと、基本的にLINQと動作が変わらないので詳細は省略
Slide 147
Slide 147 text
同期版と非同期版 • 同期版と非同期版が存在する • 同期で動くものと、async/awaitが使えるもの2種類ある • 例: Select, SelectAwait • 適宜組み合わせて使おう
Slide 148
Slide 148 text
UniTaskAsyncEnumerableの 作り方
Slide 149
Slide 149 text
作り方 • ファクトリメソッド • uGUIから変換 • 他のデータ構造から変換 • Channel • AsyncReactiveProperty
Slide 150
Slide 150 text
ファクトリメソッド • 特殊機能なUniTaskAsyncEnumerableを生成できる • Return, Empty, Never, Throw, Range, Repeat • EveryUpdate • Timer/TimerFrame • Interval/IntervalFrame • EveryValueChanged • Observableのファクトリメソッドに似てる
Slide 151
Slide 151 text
例:UniTaskAsyncEnumerable.EveryUpdate • 毎フレームメッセージを発行する
Slide 152
Slide 152 text
作り方 • ファクトリメソッド • uGUIから変換 • 他のデータ構造から変換 • Channel • AsyncReactiveProperty
Slide 153
Slide 153 text
uGUIのイベントから変換
Slide 154
Slide 154 text
作り方 • ファクトリメソッド • uGUIから変換 • 他のデータ構造から変換 • Channel • AsyncReactiveProperty
Slide 155
Slide 155 text
他のデータ構造から変換 • 各種データ構造から変換できる • IEnumerable • IObservable • Task • UniTask
Slide 156
Slide 156 text
例: IObservableからの変換 • Observableから発行されたメッセージはキューに積まれる • メッセージの取りこぼし無しでUniTaskAsyncEnumerable化できる
Slide 157
Slide 157 text
注意点 • UniTaskAsyncEnumerableは生成の仕方によっては メッセージを取りこぼすことがある • タイミングが合わなかったイベントは無視されることがある • 生成の仕方によって挙動が違う
Slide 158
Slide 158 text
取りこぼす例:EveryUpdate • ForEachAwaitAsyncを使っていると取りこぼしが起きる
Slide 159
Slide 159 text
例: 取りこぼすことを逆に利用した実装 ボタンを押したら非同期処理を走らせて、終わるまでボタン入力を無視
Slide 160
Slide 160 text
例 • 取りこぼすことがある • EveryUpdate() • uGUIのイベントから変換 • AsyncReactiveProperty • 取りこぼさない(キュー機構あり) • IObservableからの変換 • Channel
Slide 161
Slide 161 text
取りこぼしを防ぐには • 「Queue()」を使う • メッセージがバッファリングされるようになる
Slide 162
Slide 162 text
作り方 • ファクトリメソッド • uGUIから変換 • 他のデータ構造から変換 • Channel • AsyncReactiveProperty
Slide 163
Slide 163 text
Channel • UniTaskAsyncEnumerableを使ったメッセージング機構 • メッセージの送信と受信が可能 • ObservableでいうところのSubjectに相当 • Queue機能ありなのでメッセージが漏れることはない
Slide 164
Slide 164 text
例: Write
Slide 165
Slide 165 text
例: Read
Slide 166
Slide 166 text
SingleConsumerUnboundedChannel • Channelのデフォルト実装 • 単一消費者前提の実装 • 複数箇所でForEachAsync()すると正しく動作しない • 複数箇所で使いたいときはPublish()を併用する
Slide 167
Slide 167 text
例:Publishと併用 • Channelを使ったAsyncMessageBrokerの実装例 • neuecc氏がTwitterにサンプル実装貼ってたやつ • https://twitter.com/neuecc/status/1262578286493724672
Slide 168
Slide 168 text
作り方 • ファクトリメソッド • uGUIから変換 • 他のデータ構造から変換 • Channel • AsyncReactiveProperty
Slide 169
Slide 169 text
AsyncReactiveProperty • IUniTaskAsyncEnumerableベースのReactiveProperty • 基本的な挙動はUniRx.ReactivePropertyとだいたい同じ • ただawaitしたときの挙動が結構違う • Queueの機能は無し • 最新の値を1個だけ保持はしてくれるが、それより古い値は取得できない • 適宜Queueを組み合わせて使おう
Slide 170
Slide 170 text
AsyncReactivePropertyの特徴 • awaitがしやすい! • LINQと組み合わせれば柔軟にawaitができる • UniRx版はawaitしようとすると面倒くさかった
Slide 171
Slide 171 text
AsyncReactivePropertyの使い方
Slide 172
Slide 172 text
補足:UniRx版との比較 • UniRx版はawaitしにくい • Operatorを使うとIObservable扱いになっちゃう • CancellationTokenを使おうとするとUniTaskに変換しないといけない
Slide 173
Slide 173 text
使い分け • UniRx.ReactiveProperty • メッセージのブロードキャストに使う • イベント駆動の起点に • uGUI周りをMV(R)Pパターンで組むならこっち • AsyncReactiveProperty • async/awaitと組み合わせる前提のときに使う • 逐次的な非同期処理メインの場合
Slide 174
Slide 174 text
UniTaskAsyncEnumerable まとめ • 複数個の非同期処理も扱いやすくなった • 非同期処理にUniRx(Observable)を使う必要がなくなった • 新しい機構なので使い方を模索する必要あり • Event -> UniTaskAsyncEnumerable のあたりが難しい • 活用次第ではいろいろできそう…?
Slide 175
Slide 175 text
まとめ
Slide 176
Slide 176 text
UniTaskについて • Unityでasync/awaitを使うなら絶対入れるべきライブラリ • デメリットが皆無!使おう! • async/awaitのパフォーマンスの向上 • Awaiterが便利 • UniTask Trackerめっちゃ助かる • (UniRxと比べれば)学習コストそんなに高くない
Slide 177
Slide 177 text
UniTaskAsyncEnumerableについて • IAsyncEnumerableやっぱすごい • IAE自体よく考えられた機能だけあって、かなり便利 • UniRxよりは遥かにわかりやすい(簡単とは言っていない) • もとがIEnumerableなので挙動がまだわかりやすい
Slide 178
Slide 178 text
UniRxとの使い分け • async/await + UniTask, UniTaskAsyncEnumerable • 非同期処理を書く場合はこちらを優先して使うべき • 逐次処理で済む場合は間違いなくこっち • UniRx(Observable) • 単純な非同期処理にUniRxを使うべきではない • イベント処理やメッセージのブロードキャスト等にはまだまだ使えるし需 要もある
Slide 179
Slide 179 text
非同期処理における優先度 1. まずasync/await(UniTask)を検討する 2. それが無理ならUniTaskAsyncEnumerableを検討 3. それでも無理ならObservableを使う
Slide 180
Slide 180 text
イベント処理については? • 基本はUniRxの方が有利 • Observableがそもそもイベント駆動の仕組みなので • ただし他の選択肢を知っておくことも重要 • async/await, UniTask, UniTaskAsyncEumerableもイベント処理に利用可能 • UniRxの代替手段として知っておいて損はない • 相互変換もできるのでどれか一個に固執せずに組み合わせて使うのも全然アリ • 適材適所でそれぞれの弱点をカバーできるように使うとよさげ
Slide 181
Slide 181 text
最後のまとめ • async/awaitはUnityでも全然使えるよ! • むしろ無いと困るレベルで常用する • UniTaskAsyncEnumerableが便利! • 使用例をブログに書こう!(まだ枯れてないから今がチャンス!) • UniRxは使いみちを考えよう • 単発 or 逐次処理が主となるような非同期処理ではもう使うべきではなさそう • イベントメッセージの発行とか、そっち方面でまだまだ使える
Slide 182
Slide 182 text
おわり @toRisouP
Slide 183
Slide 183 text
おまけ UniTask ver2 Ver1 からの変更点の話
Slide 184
Slide 184 text
UniTask ver2 • 2020年6月、UniTaskに大型アップデートが! • UniTask1 → 2への変更点をまとめて解説
Slide 185
Slide 185 text
破壊的変更 • 最低Unityバージョン変更 • 名前空間の変更 • UniTaskのawait2度漬け禁止 • ファクトリメソッドの挙動変更 • AsyncOperation.ConfigureAwait廃止 • UniTaskの一部コンストラクタ廃止 • 一部メソッドのシグネチャ変更
Slide 186
Slide 186 text
最低Unityバージョン • 2018.4.13f1が下限になった • UniTask1系では2018.1までサポートしてた
Slide 187
Slide 187 text
名前空間が変更 • UniRx.Async -> Cysharp.Threading.Tasks • もともとUniTaskがUniRxの一部だったときの名残 • 今回のアプデで完全に決別
Slide 188
Slide 188 text
UniTaskのawait2度漬け禁止 • 同じUniTaskインスタンスを2回awaitできなくなった • パフォーマンス向上のための仕様変更 • 2回awaitすると例外が出る • 2回以上awaitしたい場合はPreserve()メソッドを呼ぶ
Slide 189
Slide 189 text
ファクトリメソッドの挙動変更 • UniTaskが起動するタイミングが「定義時」に統一 • UniTask.Delay() などが呼んだ瞬間に実行開始されるようになった
Slide 190
Slide 190 text
AsyncOperation.ConfigureAwait廃止 • 代わりにWithCancellation() / ToUniTask()を使う • あとキャンセル時にAbort()が自動で呼ばれるようにもなった
Slide 191
Slide 191 text
UniTaskのコンストラクタ廃止 • UniTask(Func> factory) が廃止 • UniTask.Lazy相当だったやつ • 代替としてUniTask.Defer / UniTask.Lazy を使おう
Slide 192
Slide 192 text
一部メソッドのシグネチャ変更 • 戻り値の変更 • UniTask.Lazy : UniTask -> AsyncLazy • UniTask.WhenAny : 組み合わせが変更 • UniTask.DelayFrame : UniTask -> UniTask • 引数の変更 • IObservable.ToUniTask() useFirstValueとCancellationTokenの順序が逆に
Slide 193
Slide 193 text
追加機能 • UniTaskAsyncEnumerable + LINQ • UniTask ver2の目玉機能
Slide 194
Slide 194 text
UniTask ver2 まとめ • 全体的にパフォーマンスが向上 • ゼロアロケーションで動作する • ノーコストでasync/awaitが使える! • 破壊的変更に注意 • awaitが2回できなくなった • 一部メソッドのシグネチャが変わってる