UniTask⼊⾨
2019/07/20
とりすーぷ
⾃⼰紹介
• とりすーぷ
• @toRisouP
• バーチャルキャスト社
• Unityエンジニア
• 最近はサーバ開発
• MagicOnion, k8s
• Microsoft MVP 2018〜
• Developer Technology
今回の内容「UniTask」
• UniTaskの概要
• UniTaskの使い⽅
• 機能紹介
• キャンセル管理について
UniTaskの概要
UniTaskとは
•Unity向けasync/await拡張ライブラリ
• Unityの標準機能ではない、⾃分で導⼊が必要な外部ライブラリ
• 株式会社Cysharpさんが提供
• 主開発はneuecc⽒(UniRxの作者)
• 前はUniRxの⼀部だったが、後に分離したライブラリ化された
• その名残で名前空間が「UniRx.Async」のまま
UniTaskでできること
• 「async/awaitでなんでもできるようになる」
• コルーチンをasync/awaitに置き換え
• Unityイベント関数のawait
• UnityEditor上でawait状況の可視化
例:コルーチンの置き換え
例:コルーチンの置き換え
コルーチンから結果を取りだすためのデリゲート
例:コルーチン -> UniTask
例:コルーチン -> UniTask
例:コルーチン -> UniTask
結果はそのままreturnすればOK
例外もthrowするだけ
UniTaskを使うメリット
• コルーチンを使う必要がなくなる
• より使いやすいasync/awaitですべてを記述できる
• 既存のTaskよりもハイパフォーマンス
• Unityに最適化されたTask-likeな機構が使える
• UniRxよりはわかりやすい
• ⼿続き処理で書ける、は⼗分メリット
導⼊⽅法
• GitHubからunitypackageをダウンロードしてインポート
• https://github.com/Cysharp/UniTask/releases
• AssetStoreにはまだ公開されていない
UniTaskが使える条件
• C# 7以降が使えるUnityであること
• Unity 2018.3 以降
• Unity 2018.2 以前はIncremental Compilerを導⼊していると使える
UniTaskの機能紹介
UniTaskが提供する機能
• UniTask型、UniTask<T>型
• UniTaskに付随するstaticメソッド群
• Unity機能のAwaiter
• UniTaskTracker
UniTaskが提供する機能
• UniTask型、UniTask<T>型
• UniTaskに付随するstaticメソッド群
• Unity機能のAwaiter
• UniTaskTracker
UniTask型/UniTask<T>型
UniTaskの機能紹介
UniTask型/UniTask<T>型
• Unity⽤に最適化されたTask-likeオブジェクト
• Task/Task<T>のUnity向け実装
• Taskの基本機能はだいたい使える
Task型 vs UniTask型
Task UniTask
機能 Unityでは不要な機能が多い Unityで活⽤できる機能のみ
オブジェクトサイズ ムダに⼤きい ⼩さい
実⾏コンテキスト管理
TaskScheduler &
SynchronizationContext
PlayerLoop
必要なC# version C# 5.0以上 C# 7.0以上
Task Tracker 無 Unity Editor上で利⽤可能
UniTask型の作り⽅
作り⽅は主に3パターン
・async/awaitの戻り値として使う
・UniTaskCompletionSourceから作る
・Observableから変換する
UniTask型の作り⽅
作り⽅は主に3パターン
・async/awaitの戻り値として使う
・UniTaskCompletionSourceから作る
・Observableから変換する
例: async/awaitから作る
• async/awaitの戻り値をUniTaskに書き換えるだけでOK
• これだけでTask型からUniTask型に変換される
例: async/awaitから作る
• ジェネリック版のときも同様
• Task<T> を UniTask<T> に置き換えるだけ
(補⾜) UniTaskVoid型
• UniTaskの機構に乗った ”async void”
• 投げっぱなし(Fire-and-forget)の⾮同期処理として使う
UniTask型の作り⽅
作り⽅は主に3パターン
・async/awaitの戻り値として使う
・UniTaskCompletionSourceから作る
・Observableから変換する
UniTaskCompletionSource
• ⼿続き処理でUniTaskを⽣成するときに使う
• UniRxのAsyncSubjectとだいたい同じ
UniTaskCompletionSource
UniTask型の作り⽅
作り⽅は主に3パターン
・async/awaitの戻り値として使う
・UniTaskCompletionSourceから作る
・Observableから変換する
Observableからの変換
• UniTaskとObservable(UniRx)は相互変換可能
注意点1
• Observableが必ず完了する状態にすること
• useFirstValue=true でTake(1)と同等になる
注意点2
• UniTask -> Observable 時、
実⾏コンテキストは必ずメインスレッドになる
UniTask/UniTask<T>型 まとめ
• 使い勝⼿は標準Taskとほとんど同じ
• 既存のasyncメソッドを機械的に置換して問題ない
• Observableが絡む時だけちょっと注意が必要
UniTaskが提供する機能
• UniTask型、UniTask<T>型
• UniTaskに付随するstaticメソッド群
• Unity機能のAwaiter
• UniTaskTracker
Staticメソッド群
UniTaskの機能紹介
UniTaskに付随するstaticメソッド群
• 特殊な挙動をするUniTaskを⽣成できるメソッド群
• FactoryMethod的なやつ
• ほとんどがawaitとの併⽤がメイン
UniTask.Run
• デリゲートをThreadPool上で実⾏する
• Task.Runと挙動はおなじ
• await後はUnityメインスレッドに戻る(configureAwait = true の時)
UniTask.Delay
• 指定秒数後に完了するUniTaskを⽣成する
• awaitすることで「⼀定時間待つ」ができる
• どのタイミングで時間計測するか選べる
• デフォルトはUpdate()タイミングで計測
UniTask.DelayFrame
• 指定フレーム後に完了するUniTaskを⽣成する
• awaitすることで「⼀定フレーム待つ」ができる
• どのタイミングで計測するか選べる
• デフォルトはUpdate()タイミング
UniTask.Yield()
• 指定のタイミングで1フレーム待機する
• 処理をメインスレッドに戻すことにも使える
• 引数なしの場合はUpdateタイミングで1フレーム待機になる
UniTask.Yield
UniTask.SwitchToThreadPool
• 実⾏コンテキストをThreadPoolに切り替える
• awaitするとそれ以降がThreadPool上での処理になる
• 似たものでSwitchToTaskPoolがあるが、こっちは⾮推奨
UniTask.SwitchToThreadPool
UniTask.SwitchToMainThread
• 実⾏コンテキストをメインスレッドに切り替える
• awaitするとそれ以降がメインスレッド上での処理になる
• UniTask.Yield(PlayerLoopTiming.Update)と微妙に挙動が異なる
UniTask.WaitUntil / UniTask.WaitWhile
• 条件を満たすまで/満たさなくなるまで待機する
• コルーチンのWaitUntil/WaitWhileと同等
• どのPlayerLoopタイミングでチェックするかも指定できる
UniTask.WaitUntil / UniTask.WaitWhile
UniTask.WaitUntilValueChanged
• 指定のオブジェクトのパラメータが変動するまで待機する
• UniRxのObserveEveryValueChangedと同じ
• 対象は弱参照で保持され、GCされるとawaitはキャンセルされる
UniTask.WhenAll
• 指定のUniTaskがすべて完了するまで待機する
• UniTask<T>の型がすべて⼀致していなくてもOK
UniTask.WhenAll
UniTask.WhenAny
• 複数のUniTaskのうちどれか1つが先に完了するまで待機する
Staticメソッド群 まとめ
• Unity向けに調整された機能が多くて便利
• 標準Taskとくらべて使いやすくなってたりもする
• まだまだあるけど量が多いので省略
UniTaskが提供する機能
• UniTask型、UniTask<T>型
• UniTaskに付随するstaticメソッド群
• Unity機能のAwaiter
• UniTaskTracker
Awaiter
UniTaskの機能紹介
Awaiterって何
• オブジェクトのawaitに必要なヤツ
• あるオブジェクトにGetAwaiter()メソッドが実装されており、
そこからAwaiterが取得できるならそのオブジェクトはawaitができる
補⾜:GetAwaiter()
• Awaiterを取得するためのメソッド
• クラスメソッドだけでなく、拡張メソッドでもOK
• ⾃前で実装すればなんでもawaitできるようになる
awaitにカーソルを合わせて定義元を参照するとどのGetAwaiter()が呼ばれるかわかる
UniTaskとAwaiter
• UniTaskはUnityのあらゆるオブジェクトの
Awaiterを提供してくれる
UniTaskが提供するAwaiter
• コルーチン
• AsyncOperation
• uGUI Event
• Unityコールバック
• JobHandle
• UnityEvent
コルーチンのAwaiter
• IEnumeratorをawaitするとコルーチンとして実⾏&待機する
コルーチンのAwaiter
(補⾜)コルーチン上でUniTask
• ToCoroutine()でUniTaskをコルーチン化もできる
AsyncOperationのAwaiter
AsyncOperationのAwaiter
• ConfigureAwaitで進⾏状況取得可能
uGUI EventのAwaiter
※using UniRx.Async.Triggers が必要
UnityコールバックのAwaiter
• MonoBehaviourに⾶んでくるイベントコールバックのこと
※using UniRx.Async.Triggers が必要
Awaiter まとめ
• Unityのあらゆる要素がawait可能になる
• コルーチンの上位互換としてasync/awaitが利⽤可能になる
UniTaskが提供する機能
• UniTask型、UniTask<T>型
• UniTaskに付随するstaticメソッド群
• Unity機能のAwaiter
• UniTaskTracker
UniTaskTracker
UniTaskの機能紹介
UniTaskTracker
• await中のUniTaskを可視化するEditor拡張
• Unity Editorの [Window] -> [UniRx] -> [UniTask Tracker]
• UniTaskがリークしていないか確認できる
UniTaskとキャンセル
⾮同期処理とキャンセル
• ⾮同期処理はキャンセルを常に考慮しなくてはいけない
• もろもろの理由で不要になった時に
実⾏中の処理を⽌めないといけない
UniTaskとキャンセル
• UniTaskを使う上でも当然キャンセルの実装が必要
• リソースの解放、適切なタイミングでの停⽌、漏れのない中断、
これらは⼈間が考えて実装する必要がある
適切にキャンセルするために
• CancellationToken
• OperationCanceledException
適切にキャンセルするために
• CancellationToken
• OperationCanceledException
CancellationToken
• ⾮同期処理にキャンセルを通知するためのC#標準機能
• 「キャンセル要求されたか?」のフラグを持つオブジェクト
CancellationToken
CancellationToken
CanellationTokenSourceから⽣成
CancellationToken
if⽂で判定
if( IsCancellationRequested ) throw new OperationCanceledException();
の省略記法
補⾜: GetCancellationTokenOnDestroy()
UniTaskとCancellationToken
• UniTaskはCancellationTokenを使ってキャンセル判定を⾏う
• キャンセル要求がくるとUniTaskはキャンセル状態になる
• await中の場合はそこで処理が終了しawait以降は実⾏されない
CancellationTokenは省略しない
• 「めんどくさいからキャンセル処理を書かない」はNG
Tokenが渡せない処理の場合のキャンセル
• CancellationTokenの状態を⼿動で確認する
• どのタイミングで処理を⽌めるのか、は⾃分で考える
• キャンセル時はOperationCanceledExceptionを発⾏すればOK
⼿動でキャンセルチェックする例
適切にキャンセルするために
• CancellationToken
• OperationCanceledException
OperationCanceledException
• UniTaskをキャンセル状態にするための特殊扱いな例外
• この例外が投げられるとUniTaskは「キャンセル状態」になる
• この例外はエラーログに出ない
Throwするタイミング
• キャンセル要求が外部からされた時にthrowする
• 外から「処理を⽌めてね」って⾔われた時に投げる例外
• ⾃⼰判断で勝⼿に投げてよい例外ではない
キャンセル要求
• キャンセル要求はCancellationTokenを通じて送られる
• ThrowIfCancellationRequested()が便利
キャンセルを上流まで伝播させる
• この例外は上流まで“貫通”させる
• try-catchを書くときはOperationCanceledExceptionだけ
素通りするようにしておく
キャンセルを上流まで伝播させる
• この例外は上流まで“貫通”させる
• try-catchを書くときはOperationCanceledExceptionだけ
素通りするようにしておく
悪⽤しない
• キャンセル⽤途以外では利⽤しないこと
• 本来の⽤途とは別の使い⽅をすると、そこから負債になる
• 「処理に失敗したときにエラーログに出したくないから、
OperationCanceledException使っちゃおう」みたいなのはNG
UniTaskとキャンセル まとめ
• CancellationTokenは可能な限り指定する
• OperationCanceledExceptionは特殊なので扱いに注意
最後のまとめ
UniTask、結局何に使えばいいの?
• 深く考えず使えそうなところでどんどん使おう
• コルーチンの代替として使える
• 他のコンポーネントが処理を終えるのを待つ、とかにも使える
• むしろUniTaskが使えないことがストレスになるくらいには便利
他の⾮同期処理との使い分けは?
• async/awaitを使うなら、UniTaskをまず使う
• 理由が無い限り標準Taskは使わない
• UniRx(Observable)は⽤途に応じて使う
• Observableはイベント処理や、結果が複数になる⾮同期処理向け
• Observableもawait可能だし、UniTaskとの相互変換もできる
まとめ
• UniTaskめっちゃ便利だから使おう!!!!
• あと今回の話の補⾜的なのをUniBook11に書きました
• さらにUniRx+UniTask本、マダカイテルカラマッテネ…

UniTask入門