🧑💻
【Unity】非同期処理:コルーチン、Async Await、Task.Run
Unityでは、非同期処理を効果的に使うことで、
ロード時間のコントロールとや、画面のフリーズを防ぐことができます。
Unityでよく使われる非同期処理についてまとめています。
1 コルーチン
Unityの独自機能で、メインスレッド上で動作する非同期処理です。
IEnumerator
を使用してフレーム単位で処理を進めることができます。
メインスレッド上で動作するため、UnityのAPIを使用できます。
反面、計算処理中にメインスレッドがブロックされるため、
ゲーム全体が「カクつく」ような動作になってしまうため、重たい処理には注意が必要です。
使用例
void Start()
{
StartCoroutine(LoadGameAssets());
}
IEnumerator LoadGameAssets()
{
yield return new WaitForSeconds(1); // ダミー待機
Debug.Log("リソースロード完了");
}
2 Async Await
async/await
は、C# 標準の非同期処理機能です。
非同期タスクを記述でき、メインスレッドで動作しながら処理を中断・再開できます。
UnityのStart
やUpdate
にasync
を付けることで利用可能です。
非同期処理が終了するとメインスレッドに復帰するため、
そのままUnity APIを使用することも可能です。
コルーチンと似たような挙動になりますが、フレーム単位の制御や、
Unity特有のタイマー処理を扱うならコルーチンの方が便利です。
使用例
async void Start()
{
Debug.Log("非同期処理開始");
// 非同期タスク(例:大量のデータの操作)を待機
string result = await LoadDataAsync();
Debug.Log("非同期処理終了");
}
void Update()
{
Debug.Log("Call Update!");
}
async Task<string> LoadDataAsync()
{
await Task.Delay(10); // バックグラウンド処理
transform.position = new Vector3(0, 1, 0); // Unity APIの操作
return "取得成功";
}
3 Task Run
Task.Run
を使用すると、バックグラウンドスレッド処理を実行し、
Unityのメインスレッドを解放できます。
ただし、UnityのAPIは複数のスレッドが同時に同じリソースにアクセスしても、
データの整合性が保たれる状態(スレッドセーフ)ではないため、
バックグラウンドスレッドから直接Unity APIを呼び出すことはできません。
使用例
async void Start()
{
Debug.Log("非同期処理開始");
// 重い処理をバックグラウンドスレッドで実行
int result = await PerformHeavyCalculation();
Debug.Log($"非同期処理終了: 計算結果 = {result}");
transform.position = new Vector3(0, 1, 0); // Unity APIの操作
}
void Update()
{
Debug.Log("Call Update!");
}
async Task<int> PerformHeavyCalculation()
{
return await Task.Run(() =>
{
Debug.Log("バックグラウンドスレッド: 重い計算処理開始");
int sum = 0;
for (int i = 0; i < 1000000000; i++)
{
sum += i;
}
Debug.Log("バックグラウンドスレッド: 計算処理完了");
return sum;
});
}
UnityのAPIを使用するには、メインスレッドに処理を戻す必要があります。
// メインスレッドの SynchronizationContext を保持
private SynchronizationContext _mainThreadContext;
void Start()
{
// 現在の SynchronizationContext(メインスレッド)を取得
_mainThreadContext = SynchronizationContext.Current;
Debug.Log("非同期処理開始");
// Task.Run を使用して非同期処理を開始
StartBackgroundTask();
}
void Update()
{
Debug.Log("Call Update!");
}
async void StartBackgroundTask()
{
Debug.Log("メインスレッド: 処理開始");
// バックグラウンドスレッドで重い計算処理を実行
int result = await Task.Run(() =>
{
Debug.Log("バックグラウンドスレッド: 重い計算処理開始");
int sum = 0;
for (int i = 0; i < 1000000000; i++)
{
sum += i;
}
Debug.Log("バックグラウンドスレッド: 計算処理完了");
return sum;
});
// メインスレッドに戻して Unity API を操作
_mainThreadContext.Post(_ =>
{
Debug.Log($"メインスレッド: 計算結果は {result}");
transform.position = new Vector3(0, 1, 0); // Unity API を操作
}, null);
Debug.Log("非同期処理終了");
}
まとめ
コルーチンが適しているケース
- フレーム単位で制御する必要がある処理。
- 軽量なタイマー処理やアニメーション制御。
- Unity APIを頻繁に使用する非同期処理。
Async Awaitが適しているケース
- 非同期I/O処理(ファイル読み書きやネットワーク通信)。
- 複雑なタスクを簡潔に記述したい場合。
Task.Runが適しているケース
- 重い計算処理をバックグラウンドで実行したい場合。
- Unity APIを使用しないデータ変換や計算処理。
Discussion