ドキュメント

Unity Engine

Unity Engine

Awaitable を使用した非同期プログラミングの概要

Understand the key features of Unity's Awaitable and how it compares to both .NET Task and iterator-based coroutines.
読み終わるまでの所要時間 11 分最終更新 1ヶ月前

Awaitable
クラスは待機可能なカスタムの Unity 型で、C# 非同期プログラミングモデルで async の戻り値の型として使用できます。以下を含む Unity の非同期 API のほとんどは、
async
と
await
のパターンをサポートします。
以下のように、
Awaitable
クラスを
await
演算子と
async
の戻り値の型の両方とともに独自のコードで使用できます。
async Awaitable<List<Achievement>> GetAchievementsAsync(){ var apiResult = await SomeMethodReturningATask(); // or any await-compatible type List<Achievement> achievements = JsonConvert.DeserializeObject<List<Achievement>>(apiResult); return achievements;}async Awaitable ShowAchievementsView(){ ShowLoadingOverlay(); List<Achievement> achievements = await GetAchievementsAsync(); HideLoadingOverlay(); ShowAchivementsList(achievements);}

Awaitable と .NET Task の比較

Awaitable
は、Unity プロジェクトの非同期コードに、.NET
Task
に代わるより効率的な方法を提供するように設計されています。
Awaitable
は効率的ですが、
Task
と比較して重要な制限があります。
最も重要な制限は、
Awaitable
インスタンスが割り当てを制限するためにプールされることです。以下の例を考えてみてください。
class SomeMonoBehaviorWithAwaitable : MonoBehavior{ public async void Start() { while(true) { // do some work on each frame await Awaitable.NextFrameAsync(); } }}
この例の
MonoBehavior
の各インスタンスは、プーリングを行わないと、フレームごとに
Awaitable
オブジェクトを割り当て、ガベージコレクターのワークロード を増加させ、パフォーマンスを低下させます。これを軽減するために、Unity は待機後に
Awaitable
オブジェクトを内部
Awaitable
プールに返します。
重要:
Awaitable
インスタンスのプーリングは、1 つの
Awaitable
インスタンスを複数回
await
することは決して安全ではないことを意味します。これを行うと、例外やデッドロックなどの未定義の動作につながる可能性があります。

Awaitable と .NET ValueTask の比較

.NET
ValueTask<TResult>
は、
Awaitable
と同じ主なメリットと制限をいくつか提供します。
ValueTask
の推奨される一般的な使用法は、ほとんどの場合に同期的に完了すると予想される非同期ワークロードです。詳細については、Understanding the Whys, Whats, and Whens of ValueTask を参照してください。

Awaitable、Task、ValueTask の要約

以下の表は、Unity の
Awaitable
クラスと .NET
Task
および
ValueTask
の機能比較をまとめたものです。

機能

Task

ValueTask

UnityEngine.Awaitable

必要な割り当て多い。
Task
を返すメソッドの呼び出しごとに割り当てられ、メモリ使用量とガベージコレクターのワークロードが増加します。
必要に応じて。
プーリングを使用して最適化できます。
必要最小限。
Awaitable
を返すメソッドを呼び出しても、通常はメモリは割り当てられません。
Awaitable
インスタンスはデフォルトでプールされるためです。
安全に複数回待機可能可。不可。
ValueTask.AsTask
で
Task
に変換する必要があります。
不可。
カスタムの
AsTask
拡張メソッドで
Task
に変換する必要があります。コード例リファレンスの 同じメソッド内での複数回の await を参照してください。
継続の非同期実行可。
同期コンテキストをデフォルトで使用します。それ以外の場合は
ThreadPool
を使用します。Unity のメインスレッドで完了するときに待ち時間が長くなります。これは、コードが再開されるために次のフレームの
Update
を待機する必要があるためです。
可。
待機中のタスクが同期的に完了するケースに最適化されています。非同期的に完了する場合、継続動作は
Task
と同じです。
不可。
継続は、完了がトリガーされると同期的に実行されます。つまり、コードは、完了がトリガーされたのと同じフレーム内ですぐに再開されます。詳細については、Awaitable の完了と継続 を参照してください。
コードによって完了をトリガー可能可。
TaskCompletionSource
を使用します。
一般的なユースケース (ほとんどの場合に同期的に完了するタスク) には適用されません。可。
AwaitableCompletionSource
を使用します。
値を返すことが可能可。
Task<TResult>
を使用します。
可。
ValueTask<TResult>
を使用します。
可。
UnityEngine.Awaitable<T>
を使用します。
WaitAll
および
WaitAny
のビルトインサポート
可。不可。
ValueTask.AsTask
で
Task
に変換する必要があります。
不可。
カスタムの
AsTask
拡張メソッドで
Task
に変換する必要があります。コード例リファレンスの .NET タスクでの Awaitable のラッピング を参照してください。
Unity スレッドと更新ループに対応した実行スケジュール不可。不可。可。
Awaitable.BackgroundThreadAsync
と
Awaitable.MainThreadAsync
を使用して、
Awaitable
を再開するスレッドを指定できます。また、
Awaitable.NextFrameAsync
と
Awaitable.FixedUpdateAsync
を使用して、
Update
または
FixedUpdate
ループに関連して処理をスケジュールすることもできます。詳細については、Awaitable の完了と継続 を参照してください。

Task または ValueTask よりも Awaitable を使用する場合

API の選択は非同期コードのパフォーマンスプロファイルによって異なりますが、一般的には以下のように選択します。
  • Task
    は、複数回待機する必要がある場合や、複数のコンシューマーから同時に待機する必要がある場合の唯一の選択肢です。
  • ValueTask
    は、ほとんどの場合に同期的に完了する高スループットの非同期コードがある場合に適しています。
  • Awaitable
    は、以下のような場合に適しています。
    • メソッドを複数回待機する必要がなく、ほとんどが非同期的に完了する場合。
    • 非同期タスクに、メインスレッド、Update ループ、FixedUpdate ループといった Unity 固有の概念のビルトインサポートが必要な場合。

Awaitable とイテレーターベースのコルーチンの比較

Awaitable
コルーチンは、通常、イテレーターベースのコルーチンよりも効率的です。特に、イテレーターが
WaitForFixedUpdate
などの null 以外の値を返す場合に適しています。
ただし、多くの
Awaitable
コルーチンを同時に実行すると、パフォーマンス上のメリットが低下します。例えば、前述のコード例のような MonoBehaviour は、
while
ループで
Awaitable.NextFrameAsync
を待機しますが、大規模なプロジェクト内のすべてのゲームオブジェクトにアタッチされると、パフォーマンスの問題が発生する可能性があります。

追加リソース