Unityにおける非同期処理入門(async/await)
コルーチンを卒業して、モダンな C# の非同期処理を使いこなそう
TL;DR
- async/await は C# 標準の非同期処理構文で、Unity でも利用できる。
TaskやUniTaskを返すメソッドにawaitを書くことで「待ち時間」を自然に記述できる。- コルーチンよりエラーハンドリングやキャンセル処理が書きやすい。
- ただし Unity の API 操作(GameObject の生成・破棄など)は メインスレッドでのみ 実行できる点に注意が必要。
1. async/await とコルーチンの違い
コルーチンは Unity 独自の仕組みですが、async/await は C# 言語の標準機能 です。Unity 2017 以降で使えます。
大きな違いをまとめると次のとおりです。
| 比較項目 | コルーチン | async/await |
|---|---|---|
| 戻り値の型 | IEnumerator | async Task / async UniTask |
| 待機の書き方 | yield return new WaitForSeconds(3) | await Task.Delay(3000) |
| 例外のキャッチ | 困難(try-catch が使いにくい) | try-catch が普通に使える |
| キャンセル処理 | StopCoroutine() | CancellationToken で制御 |
| 呼び出し元への値の返却 | 不可 | Task<T> で返せる |
2. イラストで理解する「処理の流れ」
通常のメソッド:
Start()
├─処理A
├─処理B
└─処理C ← 一気に実行
async/await を使った場合:
StartAsync()
├─処理A
├─await Task.Delay(3000) // 3秒間待機(メインスレッドをブロックしない)
│
├─処理B(3秒後に再開)
├─await Task.Yield() // 1フレーム待機
│
└─処理C(次フレームで再開)
➡ コルーチンと同様に「一度止まって、後から再開できる」のがポイントです。
3. 基本コード例
コルーチン版と async/await 版を並べて比較します。
コルーチン版:
using System.Collections;
using UnityEngine;
public class CoroutineSample : MonoBehaviour
{
void Start()
{
StartCoroutine(SampleCoroutine());
}
IEnumerator SampleCoroutine()
{
Debug.Log("処理開始");
yield return new WaitForSeconds(3);
Debug.Log("3秒後の処理");
yield return null;
Debug.Log("次のフレームで処理");
}
}
async/await 版:
using System.Threading.Tasks;
using UnityEngine;
public class AsyncSample : MonoBehaviour
{
async void Start()
{
await SampleAsync();
}
async Task SampleAsync()
{
Debug.Log("処理開始");
await Task.Delay(3000); // 3秒待つ(ミリ秒指定)
Debug.Log("3秒後の処理");
await Task.Yield(); // 1フレーム待つ
Debug.Log("次のフレームで処理");
}
}
ポイント: Start() に async void と書くことで、メソッド内で await が使えるようになります。
4. よく使う await の書き方
| 書き方 | 意味 |
|---|---|
await Task.Delay(3000) | 3秒待つ(ミリ秒指定) |
await Task.Yield() | 1フレーム待つ |
await Task.WhenAll(taskA, taskB) | 複数のタスクがすべて完了するまで待つ |
await Task.WhenAny(taskA, taskB) | いずれかのタスクが完了したら進む |
補足: WaitUntil / WaitWhile 相当の処理はループで書きます(後述)。
5. サンプルシーンで学ぶ
コルーチン版の OnboardingSequence を async/await で書き直したサンプルです。
シーン構成例
- Main Camera
- Canvas
- UI → Text – TextMeshPro(UI: ログ表示用)
- GameController(Script付き)
スクリプト例(基本版)
using System.Threading.Tasks;
using UnityEngine;
using TMPro;
public class GameController : MonoBehaviour
{
public TextMeshProUGUI logText;
async void Start()
{
await OnboardingSequence();
}
async Task OnboardingSequence()
{
logText.text = "ゲーム開始!";
await Task.Delay(2000);
logText.text = "敵が現れた!";
await Task.Delay(3000);
logText.text = "プレイヤーのターン!";
}
}
➡ 実行すると UI テキストが時間差で切り替わります。コルーチン版とほぼ同じ見た目のコードで書けます。
6. 条件待ちの書き方
コルーチンの WaitUntil に相当する処理は、while ループで代替します。
// コルーチン版
yield return new WaitUntil(() => isEnemyDefeated);
// async/await 版
while (!isEnemyDefeated)
{
await Task.Yield(); // 1フレームごとに条件をチェック
}
7. キャンセル処理(CancellationToken)
async/await の強みのひとつが、キャンセル処理を明示的に書けることです。
GameObject が破棄されたときに処理を止めたい場合に活用します。
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using TMPro;
public class GameController : MonoBehaviour
{
public TextMeshProUGUI logText;
private CancellationTokenSource _cts;
async void Start()
{
_cts = new CancellationTokenSource();
await OnboardingSequence(_cts.Token);
}
async Task OnboardingSequence(CancellationToken token)
{
logText.text = "ゲーム開始!";
await Task.Delay(2000, token);
logText.text = "敵が現れた!";
await Task.Delay(3000, token);
logText.text = "プレイヤーのターン!";
}
void OnDestroy()
{
_cts?.Cancel(); // オブジェクト破棄時にキャンセル
_cts?.Dispose();
}
}
ポイント: OnDestroy でキャンセルを呼ぶことで、シーン遷移時などに処理が残り続けるバグを防げます。
8. エラーハンドリング
コルーチンでは難しかった try-catch が、async/await では自然に書けます。
async Task LoadDataAsync()
{
try
{
await Task.Delay(1000);
// ここで例外が発生してもキャッチできる
throw new System.Exception("通信エラー!");
}
catch (System.Exception e)
{
Debug.LogError($"エラー: {e.Message}");
}
}
9. 注意点
① Unity の API はメインスレッドで呼ぶ
Task.Run() でバックグラウンドスレッドに処理を逃がすことができますが、その中から transform.position などの Unity API を呼ぶとエラーになります。重い計算だけをバックグラウンドに逃がし、Unity API の操作はメインスレッドに戻してから行いましょう。
// NG例(バックグラウンドスレッドから Unity API を触る)
await Task.Run(() =>
{
transform.position = Vector3.zero; // エラー!
});
// OK例(計算はバックグラウンド、Unity API はメインスレッドで)
var result = await Task.Run(() => HeavyCalculation());
transform.position = new Vector3(result, 0, 0); // await の後はメインスレッド
② async void は原則 Start/イベントハンドラのみ
async void はエラーが握りつぶされやすいため、内部的に呼ぶメソッドは async Task で書き、await で呼び出す設計にしましょう。
③ UniTask の活用を検討する
Unity 向けにチューニングされた UniTask(外部パッケージ)を使うと、WaitForSeconds 相当が書けたり、パフォーマンスが改善したりします。本格的に使うなら導入を検討してください。
// UniTask 版(パッケージ導入後)
await UniTask.Delay(TimeSpan.FromSeconds(3));
await UniTask.WaitForSeconds(3); // Time.deltaTime 基準
まとめ
async/await は C# 標準のモダンな非同期処理構文 で、コルーチンより読みやすく、エラー処理もキャンセルも柔軟に扱えます。
| 場面 | 推奨 |
|---|---|
| シンプルな演出・時間制御 | どちらでも OK |
| エラーハンドリングが必要 | async/await |
| キャンセル処理が必要 | async/await |
| 値を返したい | async/await(Task<T>) |
| Unity 2017 未満 | コルーチン |
コルーチンを完全に置き換える必要はありませんが、新しく書くコードには async/await を積極的に使うことで、保守性の高いゲームコードが書けるようになります。
関連記事:Unityにおけるコルーチン入門



ディスカッション
コメント一覧
まだ、コメントがありません