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回できなくなった • 一部メソッドのシグネチャが変わってる