包含此页的版本:
不含此页的版本:
这awaitoperator 暂停执行封闭的异步方法,这允许调用线程在等待时执行其他工作。当等待Task或Awaitable完成时,异步代码需要从挂起点恢复并继续执行。异步代码恢复的方式会对应用程序的功能和性能产生重要影响。
有关状态代码开始等待时所在的信息称为同步上下文。.NET 平台提供SynchronizationContext类来捕获此类信息。Task延续在调用异步方法的同步上下文中运行,如果未设置同步上下文,则通过线程池运行。
大多数 Unity API 不是线程安全的,只能从主线程调用。因此,Unity 会覆盖默认值SynchronizationContext使用自定义UnitySynchronizationContext确保所有 .NETTask默认情况下,编辑模式和播放模式下的延续都在主线程上运行。如果您调用Task-returning 方法,则延续将发布到UnitySynchronizationContext并在下一帧上运行Update勾选主线程。如果从后台线程调用它,它将在线程池线程上完成。
捕获同步上下文会增加应用程序的性能开销,并且等待下一帧更新在主线程上恢复会大规模引入延迟。您可以通过使用Awaitable相反。
除非另有说明,否则所有AwaitableUnity API 返回的实例,以及任何用户定义的实例async Awaitablereturning 方法,具有以下延续调度行为:
ThreadPool线。值得注意的例外是:
Awaitable.MainThreadAsync:延续发生在主线程上。Awaitable.BackgroundThreadAsync:延续发生在后台线程上。的影响Awaitable.MainThreadAsync和Awaitable.BackgroundThreadAsync仅对当前方法进行本地作,例如:
private async Awaitable<float> DoHeavyComputationInBackgroundAsync()
{
await Awaitable.BackgroundThreadAsync();
// here we are on a background thread
// do some heavy math here
return 42; // note: we don't need to explicitly get back to the main thread here, depending on the caller thread, DoHeavyComputationInBackgroundAsync will automatically complete on the correct one.
}
public async Awaitable Start()
{
var computationResult = await DoHeavyComputationInBackgroundAsync();
// although DoHeavyComputationInBackgroundAsync() internally switches to a background thread to avoid blocking,
// because we await it from the main thread, we also resume execution on the main thread and can safely call "main thread only APIs" such as LoadSceneAsync()
await SceneManager.LoadSceneAsync("my-scene"); // this will succeed as we resumed on main thread
}
注意:当您退出播放模式时,Unity 不会自动停止在后台运行的代码。要在退出播放模式时取消后台作,请使用Application.exitCancellationToken.
调用最有效的await Awaitable.MainThreadAsync()来自主线程和await Awaitable.BackgroundThreadAsync()来自后台线程,因为在每种情况下,代码都会在完成后立即恢复。如果从后台线程切换回主线程,则使用MainThreadAsync,则代码在主线程上的下一帧更新之前无法恢复。
如果您调用Task-返回 API 并且它不会同步完成,您至少需要等待下一个Update勾选(33fps 时为 30 毫秒)以继续运行。如果担心网络延迟,建议在主线程之外执行此作,并使用自定义逻辑在主线程和网络支持跨计算机网络进行多人游戏的 Unity 系统。更多信息
请参阅术语表任务。
在开发版本开发版本包括调试符号并启用性能分析器。更多信息
请参阅术语表,如果您尝试在多线程代码中使用 Unity API,Unity 会显示以下错误消息:
UnityException: Internal_CreateGameObject can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
重要提示:出于性能原因,Unity 不会检查非开发版本中的多线程行为,也不会在实时版本中显示此错误。虽然 Unity 不会阻止在这些上下文中执行多线程代码,但如果您确实使用多个线程,则可能会出现崩溃和其他不可预测的错误。与其使用自己的多线程,不如使用 Unity 的作业系统。作业系统安全地使用多个线程并行执行作业,实现多线程的性能优势。有关详细信息,请参阅作业系统概述。
Unity 的Awaitable类比作业系统更适合以下方案:
但是,不建议将其用于生存期较短的作,例如并行化计算密集型算法。若要充分利用多核 CPU 并并行化算法,请改用作业系统。
AwaitableCompletionSource和AwaitableCompletionSource<T>允许创建Awaitable从用户代码引发完成的实例。例如,这可用于实现用户提示,而无需实现状态机来等待用户交互完成:
public class UserNamePrompt : MonoBehaviour
{
TextField _userNameTextField;
AwaitableCompletionSource<string> _completionSource = new AwaitableCompletionSource<string>();
public void Start()
{
var rootVisual = GetComponent<UIDocument>().rootVisualElement;
var userNameField = rootVisual.Q<TextField>("userNameField");
rootVisual.Q<Button>("OkButton").clicked += ()=>{
_completionSource.SetResult(userNameField.text);
}
}
public Awaitable<string> WaitForUsernameAsync() => _completionSource.Awaitable;
}
...
public class HighScoreRanks : MonoBehaviour
{
...
public async Awaitable ReportCurrentUserScoreAsync(int score)
{
_userNameOverlayGameObject.SetActive(true);
var prompt = _userNameOverlayGameObject.GetComponent<UserNamePrompt>();
var userName = await prompt.WaitForUsernameAsync();
_userNameOverlayGameObject.SetActive(false);
await SomeAPICall(userName, score);
}
}