UniTask ではじめる Unity の async/await
コルーチンや Task のつらみを避けつつ、Unity で async/await を自然に書くための UniTask 入門。基本の書き方・実行タイミング・キャンセルまで。
Unity で「数秒待つ」「ロードを待つ」といった非同期処理は、長らくコルーチンの担当でした。UniTask は、それを C# の async/await でそのまま書けるようにするライブラリです。この記事では、なぜ UniTask なのか・基本の書き方・実行タイミング・キャンセルまでを一気に押さえます。
なぜ UniTask なのか
Unity 標準の非同期の選択肢は「コルーチン」か「Task」ですが、どちらも Unity では相性が悪い部分があります。
- コルーチン は
IEnumeratorを返すだけなので、戻り値を返せない・try/catchでyieldをまたげない・awaitできない。 - 標準の
Taskはasync/awaitが使えるものの、Taskがクラス(ヒープ確保)でありフレーム毎に回すとGCを生む。さらにシーン遷移やオブジェクト破棄で自動的に止まらない。
UniTask(Cysharp 製)は、この隙間を埋めます。
⭐
UniTaskは struct(値型) として実装され、待機の一般パスでヒープ確保ゼロ。Unity の PlayerLoop 上で回るので、スレッドを渡り歩かず待機後もそのまま Unity API を触れる。
🧭 純粋な C# の
Taskはクラスで、awaitの継続はスレッドプールに載ることがある。UniTask はメインスレッドの PlayerLoop に閉じているので、この違いを意識せず書ける。
基本の書き方
戻り値がなければ UniTask、あれば UniTask<T> を返します。await の使い勝手は素の C# と同じです。
using Cysharp.Threading.Tasks;
using UnityEngine;
public class Loader : MonoBehaviour
{
async UniTask StartAsync()
{
Debug.Log("開始");
await UniTask.Delay(1000); // 1秒待つ(コルーチンの WaitForSeconds 相当)
int value = await FetchAsync(); // 戻り値を受け取れる
Debug.Log($"取得: {value}");
}
async UniTask<int> FetchAsync()
{
await UniTask.Delay(500);
return 42; // コルーチンでは返せなかった戻り値
}
}
AsyncOperation を返す Unity の API は、そのまま await できます。
await SceneManager.LoadSceneAsync("Main"); // ロード完了まで待つ
コルーチンとの対応
コルーチンで書いていた待機は、ほぼ1対1で置き換えられます。
| コルーチン | UniTask |
|---|---|
yield return new WaitForSeconds(1f) | await UniTask.Delay(1000) |
yield return null | await UniTask.Yield() |
yield return new WaitForFixedUpdate() | await UniTask.WaitForFixedUpdate() |
yield return new WaitUntil(() => flag) | await UniTask.WaitUntil(() => flag) |
yield return op(AsyncOperation) | await op |
複数の非同期を束ねる操作も揃っています。
// 全部の完了を待つ(Task.WhenAll 相当)
await UniTask.WhenAll(LoadA(), LoadB(), LoadC());
// どれか1つ完了したら進む
int winIndex = await UniTask.WhenAny(LoadA(), LoadB());
実行タイミング(PlayerLoop)
コルーチンの yield に更新タイミングがあったように、UniTask もどのタイミングで再開するかを PlayerLoopTiming で指定できます。
await UniTask.Yield(PlayerLoopTiming.FixedUpdate); // 次の FixedUpdate で再開
await UniTask.NextFrame(); // 次フレームまで待つ
await UniTask.DelayFrame(3); // 3フレーム待つ
💡 物理演算に触る処理は
FixedUpdateタイミングで再開させると、フレーム落ち時の挙動が安定する。
キャンセルを必ず考える
UniTask を使ううえで最重要なのがキャンセルです。オブジェクトが破棄されても走り続ける非同期は、破棄済みオブジェクトへのアクセスで落ちる原因になります。CancellationToken を渡して止めましょう。
async UniTask RunAsync()
{
var ct = this.GetCancellationTokenOnDestroy(); // このオブジェクト破棄で自動キャンセル
while (true)
{
await UniTask.Delay(1000, cancellationToken: ct);
Debug.Log("生きてる");
}
// 破棄されると Delay が OperationCanceledException を投げてループを抜ける
}
⚠️ 標準の
Taskはシーン遷移やオブジェクト破棄を知らないので、放置すると裏で走り続ける。UniTask ではトークンを渡すのを習慣にする。
🧭
CancellationTokenの考え方は純粋な C# と同じ。違いは「破棄と連動したトークンを Unity 側が用意してくれる」点。
なお、戻り値も完了も待たない「撃ちっぱなし」にしたいときは Forget() を付けます。
RunAsync().Forget(); // await しない意思表示(警告も消える)
まとめ
- UniTask は Unity で
async/awaitを自然に書くためのライブラリ。コルーチンの「戻り値なし・try/catch不可・await不可」を解消する。 UniTaskは struct でヒープ確保ゼロ、PlayerLoop 上で回るので待機後もそのまま Unity API を触れる。- 待機は
UniTask.Delay/Yield/WaitUntilなどでコルーチンと1対1に置き換わる。AsyncOperationは直接awaitできる。 - 再開タイミングは
PlayerLoopTimingで制御する。 - キャンセルは必須。
GetCancellationTokenOnDestroy()のトークンを渡して、破棄後に走り続けないようにする。
次にやること: UniTask.WhenAll を使ったロード画面の並列化や、IUniTaskAsyncEnumerable によるイベントストリーム(UniRx 的な書き方)に踏み込むと、非同期の幅が一気に広がる。