本参考中的示例演示了编写异步代码时遇到的常见情况的 Awaitable 解决方案。
Unity 的测试框架无法将 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()
{
...
}
在基于迭代器的协同程序中,WaitUntil 会暂停协同程序执行,直到委托对 true 求值。您可以使用取消令牌使 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 对象会被池化以减少分配。您无法安全地多次 await 以结果结束的 Awaitable 返回方法,因为一旦返回,原始 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 封装在 Task 中。然后,可以安全地多次等待 Task:
// 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;
}