# Awaitable 代码示例参考

> Solve common asynchronous programming problems with a reference of Awaitable code examples.

本参考中的示例演示了编写异步代码时遇到的常见情况的 `Awaitable` 解决方案。

## 异步测试##asynchronous-tests

Unity 的[测试框架](/engine/6000.0/manual/scripting/environment-and-tools/testing-editortestsrunner.md)无法将 `Awaitable` 识别为有效的测试返回类型。但是，以下示例展示了如何使用 `IEnumerator` 的 `Awaitable` 实现来编写异步测试：

```lang-csharp
[UnityTest]
public IEnumerator SomeAsyncTest(){
    async Awaitable TestImplementation(){
        // test something with async / await support here
    };
    return TestImplementation();
}
```

## 框架协同程序##frame-coroutines

您可以使用 `Awaitable` 类中与框架相关的异步方法来创建异步 Unity 协同程序，以替代基于迭代器的协同程序：

```lang-csharp
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()
{
    ...
}
```

### 协程等待##conditional-wait

在基于迭代器的协同程序中，[WaitUntil](/engine/6000.0/script-reference/unity-engine/wait-until.md) 会暂停协同程序执行，直到委托对 `true` 求值。您可以使用取消令牌使 `Awaitable` 返回异步方法等待条件更改，从而为该方法创建等效行为：

```lang-csharp
public static async AwaitableUntil(Func<bool> condition, CancellationTokenSource cancellationToken)
{
   while(!condition()){
     cancellationToken.ThrowIfCancellationRequested();
     await Awaitable.NextFrameAsync();
  }
}
```

然后，您可以按如下方式传入取消令牌：

```lang-csharp
cancellationTokenSource = new CancellationTokenSource();
currentTask = AwaitableUntil(myCondition, cancellationTokenSource.Token);
```

## 异步加载资源##loading-resources-asynchronously

您可以 `await` 异步资源加载操作，使其不会阻塞主线程：

```lang-csharp
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;
}
```

## 编译##composition

您可以在同一方法中 `await` 多个不同的 `await` 兼容类型：

```lang-csharp
public async Awaitable Start()
{
    await CallSomeThirdPartyAPIReturningDotnetTask();
    await Awaitable.NextFrameAsync();
    await SceneManager.LoadSceneAsync("my-scene");
    await SomeUserCodeReturningAwaitable();
    ...
}
```

## 在 .NET Task 中封装 Awaitable##wrapping-awaitable-in-.net-task

要解决 `Awaitable` 的一些限制，可以将其封装在 .NET `Task`中。这会产生分配开销，但允许从 `Task` API 访问 `WhenAll` 和 `WhenAny` 等方法。为此，您可以编写自己的自定义 `AsTask` 扩展方法，如下所示：

```lang-csharp
// 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;
        }
    }
```

## 多次等待结果##await-a-result-multiple-times

`Awaitable` 和 `Task` 之间的主要区别在于，`Awaitable` 对象会被池化以减少分配。您无法安全地多次 `await` 以结果结束的 `Awaitable` 返回方法，因为一旦返回，原始 `Awaitable` 对象就会返回到池中。

### 不安全版本##unsafe-version

以下代码**不安全**，将导致异常和死锁：

```lang-csharp
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;
 }
```

### 安全版本##safe-version

这种情况下，您能够以分配开销[将 Awaitable 封装在 Task 中](#awaitable-as-task)。然后，可以安全地多次等待 `Task`：

```lang-csharp
// 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;
 }
```

## 其他资源##additional-resources

* [比较 Unity Awaitable 和 .NET Task](/engine/6000.0/manual/scripting/optimization/async-await-support/async-awaitable-introduction.md)
* [异步延续和完成](/engine/6000.0/manual/scripting/optimization/async-await-support/async-awaitable-continuations.md)
* [Awaitable API 参考](/engine/6000.0/script-reference/unity-engine/awaitable.md)
