Version: 6000.3
语言: 中文
使用 Awaitable 进行异步编程简介
可等待的代码示例参考

等待的完成和延续

awaitoperator 暂停执行封闭的异步方法,这允许调用线程在等待时执行其他工作。当等待TaskAwaitable完成时,异步代码需要从挂起点恢复并继续执行。异步代码恢复的方式会对应用程序的功能和性能产生重要影响。

.NET 任务延续

有关状态代码开始等待时所在的信息称为同步上下文。.NET 平台提供SynchronizationContext类来捕获此类信息。Task延续在调用异步方法的同步上下文中运行,如果未设置同步上下文,则通过线程池运行。

大多数 Unity API 不是线程安全的,只能从主线程调用。因此,Unity 会覆盖默认值SynchronizationContext使用自定义UnitySynchronizationContext确保所有 .NETTask默认情况下,编辑模式和播放模式下的延续都在主线程上运行。如果您调用Task-returning 方法,则延续将发布到UnitySynchronizationContext并在下一帧上运行Update勾选主线程。如果从后台线程调用它,它将在线程池线程上完成。

捕获同步上下文会增加应用程序的性能开销,并且等待下一帧更新在主线程上恢复会大规模引入延迟。您可以通过使用Awaitable相反。

可等待的延续

除非另有说明,否则所有AwaitableUnity API 返回的实例,以及任何用户定义的实例async Awaitablereturning 方法,具有以下延续调度行为:

  • 如果从主线程调用该方法,它将在主框架上恢复。
  • 否则,它将在 .NET 上恢复ThreadPool线。

值得注意的例外是:

  • Awaitable.MainThreadAsync:延续发生在主线程上。
  • Awaitable.BackgroundThreadAsync:延续发生在后台线程上。

的影响Awaitable.MainThreadAsyncAwaitable.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类比作业系统更适合以下方案:

  • 在以非阻塞方式处理固有异步作(例如作文件或执行 Web 请求)时简化代码。
  • 将长时间运行的任务(>1 帧)卸载到后台线程。
  • 对基于迭代器的协程进行现代化改造。
  • 等待多种异步作(帧事件、Unity 事件、第三方异步 API、I/O)。

但是,不建议将其用于生存期较短的作,例如并行化计算密集型算法。若要充分利用多核 CPU 并并行化算法,请改用作业系统

从代码触发完成

AwaitableCompletionSourceAwaitableCompletionSource<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);
    }
}

其他资源

使用 Awaitable 进行异步编程简介
可等待的代码示例参考