
Async/Await是Unity中的C#脚本功能,允许异步执行部分功能,异步函数通过等待其他事情来工作,允许中途暂停它们,直到完成特定任务。
这类似于协程在帧之间的拆分方式,函数的执行在某个点暂停,当它恢复时,从它停止的地方开始。
然而,不同之处在于,虽然协程通常有意地随着时间的推移执行逻辑,使用yield语句将函数分成多个阶段,但异步函数可用于等待另一个操作完成后再做其他事情。
这意味着将能够在后台运行昂贵的操作,而无需在此过程中停止游戏。
运作的原理
要创建异步函数,需要在方法的返回类型之前添加async关键字。
async void DoSomething()
{
// Do something!
}
但是,仅此一项还不足以创建异步函数。
为了异步运行函数的一部分,需要等待任务完成,使用await关键字和Task类型。
例如:
async void DoSomething()
{
Debug.Log("Wait for 3 seconds");
await Task.Delay(3000);
Debug.Log("3 seconds later");
}
任务是异步操作的表示。
它可能是一个延迟,如上例所示,或者是一个返回任务类型的方法,允许异步方法等待它。
而且,当它这样做时,它会停止正在做的事情,只有在等待的任务完成后才会继续。
这是将异步运行的异步函数的唯一部分,这意味着必须包含一个可等待的任务才能完全使用异步。

即使是异步函数也会被同步处理,就像其他一切一样。也就是说,直到等待一个任务,然后该任务将在后台发生。
可等待任务之前或之后的任何代码都将同步发生,阻塞主线程,就像其他任何事情一样。
这意味着,为了使用async,必须使用await,并且为了使用await ,必须有一个任务供其等待。
如何在Unity中创建任务
在可以创建任何类型的任务之前,需要将System Threading Tasks命名空间添加到脚本的顶部。
例如:
using UnityEngine;
using System.Threading.Tasks;
完成后,将能够使用和创建Tasks。
任务类代表一种异步操作。
重要的是,它的进度是可跟踪的,这允许异步函数等待任务完成,而无需停止进程中的其他所有内容。
可以用任务做什么?
一种选择是通过让出它来暂停当前函数的执行,就像在协程中使用Yield语句一样。
例如,While循环会继续执行,直到其条件不再为真。
例如:
void CountToTen()
{
int number = 0;
while (number < 10)
{
number++;
Debug.Log(number);
}
Debug.Log("I've finished counting!");
}
在常规函数中,这意味着它在同一帧中同时发生。
但是,这很容易引起问题,因为除非其内容允许它跳出自己的循环,否则在常规函数中运行while循环会导致Unity冻结。
发生这种情况是因为Unity同步处理while循环,这意味着它无法执行任何其他操作,直到循环的条件不再为真。
但是,使用Task产生循环可以暂停函数的执行,从而允许它在多个帧上进行处理,而不仅仅是一个帧。
例如:
async void CountToTenAsync()
{
int number = 0;
while (number < 10)
{
number++;
Debug.Log(number);
await Task.Yield();
}
Debug.Log("I've finished counting!");
}
这意味着,即使你的while循环包含一个无限真实的条件,因为它是使用任务产生的,它只是意味着它会在每一帧发生,而不是永远在一帧中发生。
但是,如果不想等到下一帧怎么办?如果想等待一段时间怎么办?
就像协程一样,可以将函数暂停一段特定的时间,之后函数将通过等待延迟任务从中断的地方继续。
例如:
async void Wait()
{
Debug.Log("Give me a second...");
await Task.Delay(1000);
Debug.Log("Ok, I'm done.");
}
与协程中的Wait For Seconds不同, Task Delay接受以毫秒为单位的持续时间,而不是秒。
这意味着,如果想将秒数传递给延迟参数,则需要将持续时间乘以1000。
例如:
async void Wait()
{
// Waits 2.5 seconds
await Task.Delay((int)2.5 * 1000);
}
虽然为下一帧让步,或者在做其他事情之前等待一定时间过去,这两种方法都很有用,但它们通常可以使用协程解决的问题。
这意味着,除了工作流程的差异之外,对于这些类型的任务,使用异步而不是协程并没有真正的好处。
相反,为了充分利用异步,可能会发现等待实际函数完成更有用。
那如何做呢?
等待函数在后台完成的一种方法是等待返回Task类型的异步函数。
例如:
async void Start()
{
await MyFunction();
Debug.Log("All Done!");
}
async Task MyFunction()
{
// Waits 5 seconds
await Task.Delay(5000);
}
但是…
为了使其工作,等待的任务本身必须等待其他任务。其中,如果正在做一些返回任务类型的事情,例如将某事延迟一段时间,这不是问题。
但是…
如果只想执行一段可能需要一段时间才能处理的代码,但实际上并没有等待任何东西,那么如果想异步处理它,则需要将其传递给Task将方法作为匿名函数运行。
例如:
async void Start()
{
await Task.Run(() => Count());
Debug.Log("All Done!");
}
void Count()
{
for (int i = 0; i < 10000; i++)
{
Debug.Log(i);
}
}
这通常允许同步代码块在后台运行。
何时使用异步或等待?
Unity中异步任务的主要好处是,与常规函数不同,它们不会阻塞主线程。这意味着可以使用async故意在后台执行昂贵的操作,这样它就不会在游戏运行时完全停止游戏,从而改善玩家的整体体验。
例如:
Unity的一些内置功能,如加载场景,或导入大型资产,如音频数据,正是出于这个原因,已经提供了异步替代方案。
作为异步如何有用的示例,它们是后台操作的良好候选者,因为它们通常需要很长时间才能完成,但在处理它们时停止游戏是不合适的。
虽然async/await工作流,如果项目中有类似的任务,可能需要一段时间才能运行,但并不一定需要在游戏停止时停止,它允许执行相同的操作。
但是,为什么要停在那里?
如果async/await工作流更容易使用,而且通常不会阻塞主线程,为什么不使用它来完全取代协程呢?
该问题可以参阅站内文章:Unity中的异步与协程。
….
以上是在Unity中关于异步如何使用的全部内容,如果你有任何反馈,请随时在本页面下方留言。