このリファレンスの例は、非同期コードの記述時に経験する一般的なシナリオに対する、Awaitable での解決策を示しています。
Unity の Test Framework は、Awaitable を有効なテストの戻り値の型として認識しません。しかし、以下の例は、IEnumerator の Awaitable 実装を使用して非同期テストを記述する方法を示しています。
[UnityTest]
public IEnumerator SomeAsyncTest(){
async Awaitable TestImplementation(){
// test something with async / await support here
};
return TestImplementation();
}
Awaitable クラスのフレーム関連の非同期メソッドを使用して、イテレーターベースのコルーチンの代わりに、非同期の Unity コルーチンを作成できます。
async Awaitable SampleSchedulingJobsForNextFrame()
{
// Wait until end of frame to avoid competing over resources with other Unity subsystems
await Awaitable.EndOfFrameAsync();
var jobHandle = ScheduleSomethingWithJobSystem();
// Let the job execute while the next frame starts
await Awaitable.NextFrameAsync();
jobHandle.Complete();
// Use results of computation
}
JobHandle ScheduleSomethingWithJobSystem()
{
...
}
イテレーターベースのコルーチンでは、デリゲートが true を評価するまで、WaitUntil はコルーチンの実行をサスペンドします。キャンセルトークンを使用して条件が変更されるまでメソッドを待機させることで、Awaitable を返す非同期メソッドと同等の動作を作成できます。
public static async AwaitableUntil(Func<bool> condition, CancellationTokenSource cancellationToken)
{
while(!condition()){
cancellationToken.ThrowIfCancellationRequested();
await Awaitable.NextFrameAsync();
}
}
その後、以下のようにキャンセルトークンを渡すことができます。
cancellationTokenSource = new CancellationTokenSource();
currentTask = AwaitableUntil(myCondition, cancellationTokenSource.Token);
非同期のリソースロード操作を await して、メインスレッドをブロックしないようにすることができます。
public async Awaitable Start()
{
// Load texture resource asynchronously
var operation = Resources.LoadAsync("my-texture");
// Return control to the main thread while the resource loads
await operation;
var texture = operation.asset as Texture2D;
}
同じメソッド内で複数の異なる await に互換性のある型を await できます。
public async Awaitable Start()
{
await CallSomeThirdPartyAPIReturningDotnetTask();
await Awaitable.NextFrameAsync();
await SceneManager.LoadSceneAsync("my-scene");
await SomeUserCodeReturningAwaitable();
...
}
Awaitable の制限のいくつかを回避するために、.NET Task でラップします。これには割り当ての手間がかかりますが、Task API から WhenAll や WhenAny などのメソッドにアクセスできます。これを行うには、独自のカスタム AsTask 拡張メソッドを次のように記述します。
// Implement custom AsTask extension methods to wrap Awaitable in Task
public static class AwaitableExtensions
{
public static async Task AsTask(this Awaitable a)
{
await a;
}
public static async Task<T> AsTask<T>(this Awaitable<T> a)
{
return await a;
}
}
Awaitable と Task の主な違いは、Awaitable オブジェクトは割り当てを減らすためにプールされることです。Awaitable を返すメソッド (1 つの結果を複数回返すことで完了する) を、安全に await することはできません。いったん返されると、元の Awaitable オブジェクトはプールに返されるためです。
以下のコードは 安全ではなく、例外とデッドロックの原因になります。
async Awaitable Bar(){
var taskWithResult = SomeAwaitableReturningFunction();
var awaitOnce = await taskWithResult;
// Do something
// The following will cause errors because at this point taskWithResult has already been pooled back
var awaitTwice = await taskWithResult;
}
これは、割り当ての手間をかけて、タスクで Awaitable をラップ できるシナリオの 1 つです。こうすると、Task は複数回安全に await できます。
// Implement custom AsTask extension methods to wrap Awaitable in Task
public static class AwaitableExtensions
{
public static async Task AsTask(this Awaitable a)
{
await a;
}
public static async Task<T> AsTask<T>(this Awaitable<T> a)
{
return await a;
}
}
async Awaitable Bar(){
var taskWithResult = SomeAwaitableReturningFunction();
// Wrap the returned Awaitable in a Task
var taskWithResultAsTask = taskWithResult.AsTask();
// The task can now be safely awaited multiple times, at the cost of allocating
var awaitOnce = await taskWithResultAsTask;
// Do something
var awaitTwice = await taskWithResultAsTask;
}