【推荐100个unity插件】比 Unity 自带协程更高效的异步处理方式,高性能和0GC的async/await异步方案——UniTask插件

注意:考虑到后续接触的插件会越来越多,我将插件相关的内容单独分开,并全部整合放在【推荐100个unity插件】专栏里,感兴趣的小伙伴可以前往逐一查看学习。

文章目录

前言

1、UniTask 是什么?

UniTask 是专为 Unity 设计的高性能异步编程库,它提供了比 C# 原生 Task 更轻量、更高效的异步解决方案。简单来说,它让 Unity 中的异步编程(比如加载资源、等待时间、网络请求等)变得更简单、更快速,而且不产生GC内存垃圾

如果你还不理解什么是GC,可以参考:【从零开始入门unity游戏开发之——C#篇04】栈(Stack)和堆(Heap),值类型和引用类型,以及特殊的引用类型string,垃圾回收(GC)

2、为什么需要 UniTask?

想象你在做游戏时需要:

  • 等待 3 秒后显示一个提示
  • 加载一个大资源时不卡住游戏
  • 等待玩家点击按钮后再继续

传统做法是用协程(Coroutine)C#的Task

协程知识可以参考:【零基础入门unity游戏开发——unity通用篇27】协同程序协程(Coroutine)的使用介绍

Task多线程相关知识可以参考:【从零开始入门unity游戏开发之——C#篇37】进程、线程、C# 中实现多线程有多种方案和async/await异步编程

但它们都有缺点:

技术方案主要问题GC压力线程安全易用性
协程(Coroutine)不能返回值,异常处理困难主线程一般
C# Task与Unity主线程调度不兼容多线程复杂
UniTask几乎为零自动同步优秀

UniTask专为Unity设计,解决了上述所有痛点,提供了:

  • 零GC的高性能异步操作
  • 完美的Unity主线程集成
  • 直观的async/await语法
  • 丰富的Unity特定功能

一、基础入门

1、UniTask官方地址

  • github地址:https://github.com/Cysharp/UniTask
  • 码云地址:https://gitee.com/unity_data/UniTask

2、安装 UniTask

  1. 从 GitHub 下载最新版本:https://github.com/Cysharp/UniTask/releases
    在这里插入图片描述
  2. 下载 .unitypackage 文件并导入到你的项目中
  3. 在代码中添加命名空间:using Cysharp.Threading.Tasks;

注意:UniTask 功能依赖于 C# 7.0,所以需要的 Unity 最低版本是Unity 2018.3 ,官方支持的最低版本是Unity2018.4.13f1.

3、第一个UniTask示例

async UniTaskVoid StartCountdown()
{
    for (int i = 3; i > 0; i--)
    {
        Debug.Log($"倒计时: {i}");
        await UniTask.Delay(1000); // 等待1秒
    }
    Debug.Log("延迟调用结束");
}

// 调用
void Start()
{
    StartCountdown().Forget(); // Forget表示不等待结果
}

4、UniTask vs UniTaskVoid

  • UniTask:需要await等待的任务,可以返回值
async UniTask<int> LoadDataAsync()
{
    await UniTask.Delay(500);
    return 42; // 返回结果
}
  • UniTaskVoid:"即发即忘"的任务,不返回结果
async UniTaskVoid ShowEffect()
{
    await UniTask.Delay(300);
    PlayParticleEffect();
}

📌 最佳实践:async UniTaskVoid是async UniTask的轻量级版本。优先使用UniTask,只有确定不需要等待的任务才用UniTaskVoid

6、调用

async void是一个原生的 C# 任务系统,因此它不在 UniTask 系统上运行。避免 async void,尽量使用 async UniTaskasync UniTaskVoid。如果您不需要等待(即发即弃),那么使用UniTaskVoid会更好。不幸的是,要解除警告,您需要在尾部添加Forget()或者使用_ =

❌ 错误做法:

async void Start() // 避免使用async void
{
    await LoadData();
}

✅ 正确做法:

async UniTaskVoid Start() // 使用UniTaskVoid
{
    await StartCountdown();
}

// 或者

void Start()
{
    LoadData().Forget(); // 明确忽略结果
    //或者 _ = StartCountdown();
}

二、核心功能详解

1、延时操作

UniTask 提供了多种延时方式:

// 等待1秒(受Time.timeScale影响)
await UniTask.Delay(1000);

// 使用 TimeSpan 等待1秒(受Time.timeScale影响)
await UniTask.Delay(TimeSpan.FromSeconds(1));

// 等待1秒(不受Time.timeScale影响)
await UniTask.Delay(1000, ignoreTimeScale: true);

// 等待当前帧结束。类似于协程中的 WaitForEndOfFrame。
await UniTask.WaitForEndOfFrame();
//需要注意的是,在 `Unity 2023.1 之前`的版本中,WaitForEndOfFrame 需要传入一个 `MonoBehaviour` 实例作为参数。
// await UniTask.WaitForEndOfFrame(this);
        
// 等待下一帧
await UniTask.NextFrame();

// 等待固定60帧
await UniTask.DelayFrame(60);

// 默认在 Update 之后执行。默认情况下 `UniTask.Yield()` 等同于 `UniTask.Yield(PlayerLoopTiming.Update)`
// 协程 yield return null 的替代方案
await UniTask.Yield();// 让出执行权,下一帧继续
// 等待物理更新后执行,等同于await UniTask.WaitForFixedUpdate();协程yield return new WaitForFixedUpdate 的替代方案
await UniTask.Yield(PlayerLoopTiming.FixedUpdate);

// 在 LateUpdate 阶段后继续
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); 

常见 PlayerLoopTiming 选项

可以通过指定不同的 PlayerLoopTiming,可以控制代码在 Unity 生命周期的不同阶段执行。
在这里插入图片描述

  • EarlyUpdate: Unity 早期更新阶段
  • FixedUpdate: 物理更新阶段
  • PreUpdate: 更新前阶段
  • Update: 常规 Update 阶段
  • PreLateUpdate: LateUpdate 前阶段
  • PostLateUpdate: 一帧完全结束后(最常用)

2、线程切换

// 切换到线程池线程执行耗时操作
await UniTask.SwitchToThreadPool();

// TODO:执行耗时计算

// 返回主线程
// await UniTask.Yield();
await UniTask.SwitchToMainThread();

3. 等待条件满足

3.1 UniTask.WaitUntil:等待条件成立

async UniTaskVoid WaitUntilCondition()
{
    await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
    Debug.Log("玩家按下了空格键!");
}

3.2 UniTask.WhenAll:同时等待多个条件满足

// 同时执行三个任务,全部完成后继续
await UniTask.WhenAll(
    Task1(),
    Task2(),
    Task3()
);

实战:同时等待多个小球达到目标位置

using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;

public class WhenAllExample : MonoBehaviour
{
    public GameObject ball1;
    public GameObject ball2;
    private CancellationTokenSource _cancellationTokenSource;

    void Start()
    {
        _cancellationTokenSource = new CancellationTokenSource();
        WaitForAllBallsToReachPosition(_cancellationTokenSource.Token).Forget();
    }

    async UniTaskVoid WaitForAllBallsToReachPosition(CancellationToken token)
    {
        // 分别创建两个等待任务,监控 ball1 和 ball2 的 x 坐标
        var task1 = UniTask.WaitUntil(() => ball1.transform.position.x > 1, cancellationToken: token);
        var task2 = UniTask.WaitUntil(() => ball2.transform.position.x > 1, cancellationToken: token);

        // 使用 WhenAll 同时等待两个任务完成
        await UniTask.WhenAll(task1, task2);

        // 所有等待条件满足后,修改小球颜色
        ball1.GetComponent<Renderer>().material.color = Color.blue;
        ball2.GetComponent<Renderer>().material.color = Color.red;
    }

    private void OnDestroy()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}

这种方式适用于需要等待多个并行条件同时成立的场景,如多个动画结束、多个任务完成等。

3.3 UniTask.WhenAny:等待任意一个条件满足

有时我们希望用户只点击任意一个按钮就能触发下一步操作。在示例中,我们对两个按钮的点击事件分别设置等待条件,并通过 UniTask.WhenAny 来等待任一条件满足,满足后输出提示信息。

// 任意一个任务完成就继续
await UniTask.WhenAny(
    Task1(),
    Task2(),
    Task3()
);

实战:等待任意一个按钮点击

using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;

public class WhenAnyExample : MonoBehaviour
{
    public bool _isClick1;
    public bool _isClick2;
    private CancellationTokenSource _cancellationTokenSource;

    void Start()
    {
        _cancellationTokenSource = new CancellationTokenSource();
        WaitForAnyButtonClick(_cancellationTokenSource.Token).Forget();
    }

    async UniTaskVoid WaitForAnyButtonClick(CancellationToken token)
    {
        // 分别创建等待任务,监控 _isClick1 和 _isClick2 状态
        var task1 = UniTask.WaitUntil(() => _isClick1, cancellationToken: token);
        var task2 = UniTask.WaitUntil(() => _isClick2, cancellationToken: token);

        // 使用 WhenAny 等待任意一个任务完成
        await UniTask.WhenAny(task1, task2);

        // 当任意一个按钮被点击后,输出提示信息
        Debug.Log("一个按钮被点击了");
    }


    private void OnDestroy()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}

这种方式可以用在用户输入、系统状态变化等需要响应第一个满足条件的场景。

3.4 UniTask.WaitUntilValueChanged:等待值发生变化

UniTask.WaitUntilValueChanged 用于等待某个值发生变化,然后继续执行后续代码。

// 等待值变化
await UniTask.WaitUntilValueChanged(transform, t => t.position);

实战:

using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;

public class WaitUntilValueChangedExample : MonoBehaviour
{
    public GameObject ball;
    private CancellationTokenSource _cancellationTokenSource;

    void Start()
    {
        // 创建一个新的 CancellationTokenSource 实例,用于取消异步操作
        _cancellationTokenSource = new CancellationTokenSource();

        // 调用 WaitUntilPositionChanges 方法,并传递 CancellationToken
        // 使用 Forget 方法忽略返回的 Task,这样可以避免异步方法的结果未被处理导致的编译警告
        WaitUntilPositionChanges(_cancellationTokenSource.Token).Forget();
    }

    async UniTaskVoid WaitUntilPositionChanges(CancellationToken token)
    {
        // 第一个参数是要监视的对象,这里是球的transform
        // 第二个参数是一个Func委托,用于获取要监视的值,这里是transform的position属性
        // cancellationToken参数用于传递一个取消令牌,可以在需要时取消等待操作
        await UniTask.WaitUntilValueChanged(ball.transform, x => x.position, cancellationToken: token);
        Debug.Log("小球位置已变化");
    }

    private void OnDestroy()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}
  • 在上述示例中,WaitUntilPositionChanges 方法会等待直到 ball 的位置发生变化,然后输出日志信息。
  • UniTask.WaitUntilValueChanged 方法接受一个对象和一个用于监视该对象某个属性或字段的委托,当该属性或字段的值发生变化时,等待结束,继续执行后续代码。

4、资源加载

4.1 普通资源加载

方式一 加载本地资源
async UniTask<Texture2D> LoadTexture(string path)
{
    // 异步加载资源
    var resource = await Resources.LoadAsync<Texture2D>(path);
    return (Texture2D)resource.asset;
}
方式二 加载远程资源
async UniTask<string> FetchWebData(string url)
{
    using var request = UnityWebRequest.Get(url);
    await request.SendWebRequest();
    
    if (request.result == UnityWebRequest.Result.Success)
    {
        return request.downloadHandler.text;
    }
    else
    {
        throw new Exception($"请求失败: {request.error}");
    }
}

4.2 带进度回调资源加载

方法一 加载资源
using Cysharp.Threading.Tasks;
using UnityEngine;

public class ResUniTask : MonoBehaviour
{
    void Start() {
        LoadAsyncUniTask();
    }

    async void LoadAsyncUniTask()
    {
        ResourceRequest res = Resources.LoadAsync<GameObject>("Prefabs/Cube");

        // 通过ToUniTask获取进度
        await res.ToUniTask(Progress.Create<float>(p => 
        {
            Debug.Log($"加载进度: {p:P0}");
        }));

        // 直接获取资源
        //var asset = await res;

        if (res.asset != null)
        {
            //实例化资源
            Instantiate(res.asset);
        }
        else
        {
            Debug.LogError("[UniTask加载] 资源加载失败!");
        }
    }
}
方法二 分步加载场景
async UniTaskVoid LoadSceneAsync(string sceneName)
{
    // 显示加载界面
    ShowLoadingScreen();
    
    // 异步加载场景
    var operation = SceneManager.LoadSceneAsync(sceneName);
    
    // 更新进度条
    while (!operation.isDone)
    {
        UpdateProgressBar(operation.progress);
        await UniTask.Yield();
    }
    
    // 隐藏加载界面
    HideLoadingScreen();
}

三、高级特性

1、取消操作

1.1 使用 CancellationToken 取消等待操作

在异步操作中,使用 CancellationToken 可以在需要时取消等待操作,防止出现无限等待的情况。

using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;
using System;

public class CancellationExample : MonoBehaviour
{
    private CancellationTokenSource _cancellationTokenSource;

    void Start()
    {
        // 创建一个新的 CancellationTokenSource 实例,用于生成取消令牌
        _cancellationTokenSource = new CancellationTokenSource();

        // 调用 PerformCancelableTask 方法,并传递生成的取消令牌
        PerformCancelableTask(_cancellationTokenSource.Token).Forget();

        Debug.Log("Start完成");
    }

    // 定义一个异步方法 PerformCancelableTask,用于执行一个可取消的任务
    async UniTaskVoid PerformCancelableTask(CancellationToken token)
    {
        try
        {
            // 使用 UniTask.Delay 方法延迟 5000 毫秒(5 秒),同时传入 cancellationToken 参数 token 以支持任务取消
            await UniTask.Delay(5000, cancellationToken: token);

            // 如果任务未被取消,延迟结束后输出 "任务完成"
            Debug.Log("任务完成");
        }
        catch (OperationCanceledException)
        {
            // 如果在等待过程中任务被取消,会抛出 OperationCanceledException 异常,捕获该异常并输出 "任务被取消"
            Debug.Log("任务被取消");
        }
    }

    void OnDestroy()
    {
        // 调用 _cancellationTokenSource 的 Cancel 方法,取消任何正在进行的异步操作。
        _cancellationTokenSource.Cancel();
        // 调用 _cancellationTokenSource 的 Dispose 方法,释放该对象占用的资源。
        _cancellationTokenSource.Dispose();
    }
}

在上述示例中,PerformCancelableTask 方法会等待 5 秒钟,然后输出“任务完成”。如果在这 5 秒内对象被销毁,OnDestroy 方法会取消该任务,并输出“任务被取消”。

1.2 CreateLinkedTokenSource 多条件取消实现

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class UniTaskCreateLinkedTokenSource : MonoBehaviour
{
    public Button cancelButton;
    private CancellationTokenSource cancelToken;
    private CancellationTokenSource timeoutToken;
    void Start()
    {
        cancelToken = new CancellationTokenSource();
        cancelButton.onClick.AddListener(() =>
        {
            cancelToken.Cancel(); // 点击按钮后取消。
        });

        timeoutToken = new CancellationTokenSource();
        timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时。

        BeginTestCancelAfter().Forget();
    }

    async UniTaskVoid BeginTestCancelAfter()
    {
        try
        {
            // 链接 token
            var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token);

            int i = 0;
            while (true)
            {
                i++;
                Debug.Log($"执行任务{i}次");

                // 等待 1 秒,并传入取消令牌,若取消则会抛出异常
                await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: linkedTokenSource.Token);
            }
        }
        catch (OperationCanceledException ex)
        {
            if (timeoutToken.IsCancellationRequested)
            {
                Debug.Log("超时退出");
            }
            else if (cancelToken.IsCancellationRequested)
            {
                Debug.Log("点击取消退出");
            }
        }
    }
}

1.3 GetCancellationTokenOnDestroy绑定GameObject取消

CancellationToken 不止可以由CancellationTokenSource,还可以使用 MonoBehaviour 的GetCancellationTokenOnDestroy扩展方法创建。

using System;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskGetCancellationTokenOnDestroy : MonoBehaviour
{
    void Start()
    {
        BeginTestCancel().Forget();
    }

    async UniTaskVoid BeginTestCancel()
    {
        try
        {
            int i = 0;
            while (true)
            {
                i++;
                Debug.Log($"执行任务{i}次");

                // 等待 1 秒,当this GameObject Destroy的时候,就会执行Cancel
                await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: this.GetCancellationTokenOnDestroy());
            }
        }
        catch (OperationCanceledException)
        {
            // 捕获到取消异常,打印信息
            Debug.Log("捕获到取消异常OperationCanceledException");
        }
    }
}

1.4 CancelAfterSlim延迟取消

超时是取消操作的变体。您可以通过 CancellationTokenSouce.CancelAfterSlim(TimeSpan) 设置超时,并将 CancellationToken 传递给异步方法。

using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;

public class UniTaskCancelAfter : MonoBehaviour
{
    private CancellationTokenSource _cancellationTokenSource;

    void Start()
    {
        // 创建一个新的取消令牌源实例
        _cancellationTokenSource = new CancellationTokenSource();

        // 启动一个异步任务
        UniTaskVoid taskVoid = BeginTestCancelAfter(_cancellationTokenSource.Token);

        // 设置在指定时间(这里是 3 秒)后触发取消请求,实现超时处理
        _cancellationTokenSource.CancelAfterSlim(TimeSpan.FromSeconds(3));

        Debug.Log("Start完成");
    }

    async UniTaskVoid BeginTestCancelAfter(CancellationToken token)
    {
        try
        {
            int i = 0;
            while (true)
            {
                i++;
                Debug.Log($"执行任务{i}次");
                // 等待 1 秒,并传入取消令牌,若取消则会抛出异常
                await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: token);
            }
        }
        catch (OperationCanceledException)
        {
            // 捕获到取消异常,打印信息
            Debug.Log("捕获到取消异常OperationCanceledException");
        }
    }
}

结果
在这里插入图片描述

2、超时处理

SomeAsyncOperation 超时处理

try
{
    // 3秒超时
    await SomeAsyncOperation().Timeout(TimeSpan.FromSeconds(3));
}
catch (TimeoutException)
{
    Debug.Log("操作超时");
}

3、UniTask监听UGUI

UniTask 在 UI 事件处理中的核心优势

  • 无阻塞异步操作
    利用 async/await 机制,确保 UI 事件处理在后台进行,不会阻塞主线程,从而保障流畅的用户体验。

  • 轻量高效的实现
    采用结构体和对象池的设计,大幅降低垃圾回收压力,使得高频事件处理更加稳定。

  • 灵活的事件调度
    结合 CancellationToken 和异步 LINQ,不仅能够精细控制事件处理流程,还能轻松应对复杂的交互逻辑。

示例一:按钮不同监听方式

public Button myButton;

async UniTaskVoid HandleButtonClick()
{
    // 等待按钮被点击
    await myButton.OnClickAsync();
    Debug.Log("按钮被点击了!");
}

示例二:判断单击与双击

在 OnClickBtn2UniTask 方法中,首先等待第一次点击,然后在 1 秒内判断是否有第二次点击,从而区分单击和双击操作。

private async UniTaskVoid OnClickBtn2UniTask(CancellationToken cancellationToken)
{
    while (true)
    {
        // 等待第一次点击
        var firstClickUniTask = btn2.OnClickAsync(cancellationToken);
        await firstClickUniTask;
        Debug.Log("OnClickBtn2UniTask 第1次点击");

        // 等待第二次点击或 1 秒超时
        int index = await UniTask.WhenAny(
            btn2.OnClickAsync(cancellationToken),
            UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cancellationToken)
        );

        if (index == 0)
        {
            // 第二次点击在 1 秒内发生
            Debug.Log("OnClickBtn2UniTask 时间间隔不超过1");
        }
        else
        {
            // 1 秒超时,视为单击
            Debug.Log("OnClickBtn2UniTask 时间间隔超过1");
        }
    }
}

示例三:异步 LINQ 监听三连点击

使用异步 LINQ 操作,在 OnClickBtn3TripleClick 方法中等待按钮被点击三次后再执行后续代码,适用于需要捕捉用户快速连续操作的场景。

/// <summary>
/// 该方法使用异步 LINQ 监听 btn3 三次点击。
/// 注意:该方法只会监听一次三连点击,不会持续监听。
/// 如果需要持续监听,需要自己加while
/// </summary>
/// <param name="cancellationToken"></param>
private async UniTaskVoid OnClickBtn3TripleClick(CancellationToken cancellationToken)
{
    // 使用异步 LINQ 等待 btn3 被点击三次
    // await btn3.OnClickAsAsyncEnumerable().Take(3).LastAsync(cancellationToken);
    await btn3.OnClickAsAsyncEnumerable().Take(3)
        .ForEachAsync(_ => { Debug.Log("OnClickBtn3TripleClick: 每次点击触发"); }, cancellationToken);

    // // index从0开始 第一次点击index会是0 所以取到的是0 1 2 index为2时 也就是第三次点击 所以会输出三次点击完成
    // var asyncEnumerable = btn3.OnClickAsAsyncEnumerable();
    // await asyncEnumerable.Take(3).ForEachAsync
    // ((_, index) =>
    // {
    //     if (cancellationToken.IsCancellationRequested) return;
    //
    //     if (index == 0)
    //     {
    //         Debug.Log(0);
    //     }
    //     else if (index == 1)
    //     {
    //         Debug.Log(1);
    //     }
    //     else
    //     {
    //         Debug.Log(2);
    //     }
    // }, cancellationToken);

    Debug.Log("OnClickBtn3TripleClick: 三次点击完成");
}

示例四:事件排队处理

在 OnClickBtn4QueueEvents 方法中,通过事件排队的方式处理按钮点击,每次点击事件处理之间等待固定时间,确保事件顺序执行而不会互相干扰。

private async UniTaskVoid OnClickBtn4QueueEvents(CancellationToken cancellationToken)
{
    int count = 0;
    await btn4.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync(async _ =>
    {
        Debug.Log($"OnClickBtn4QueueEvents: 开始处理点击事件{count}");
        await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: cancellationToken);
        Debug.Log($"OnClickBtn4QueueEvents: 点击事件{count}处理完成");
        count++;
    }, cancellationToken);
}

示例五:监听输入框结束编辑事件

使用 OnInputFieldEndEdit 方法,通过异步流实时监听输入框结束编辑事件,并输出用户输入的内容。此方式适合需要捕捉用户输入并即时反馈的场景。

private async UniTaskVoid OnInputFieldEndEdit(CancellationToken cancellationToken)
{
    // 监听输入框结束编辑事件
    await foreach (var text in inputField.OnEndEditAsAsyncEnumerable().WithCancellation(cancellationToken))
    {
        Debug.Log($"OnInputFieldEndEdit: 输入框结束编辑,输入内容为: {text}");
    }
}

示例六:监听 Toggle 值变化

在 OnToggleValueChangedAsync 方法中,通过异步流监听 Toggle 的值变化事件,每次变化时输出当前值,使状态变更能够及时反馈到日志中。

private async UniTaskVoid OnToggleValueChangedAsync(CancellationToken cancellationToken)
{
    await toggle.OnValueChangedAsAsyncEnumerable(cancellationToken)
        .ForEachAsync(value => { Debug.Log("Toggle 值变化:" + value); });
}

示例七:监听 Slider 值变化

类似于 Toggle,OnSliderValueChangedAsync 方法通过异步流监听 Slider 数值变化,每次变化都输出当前值,方便对滑动条数值的动态监控。

private async UniTaskVoid OnSliderValueChangedAsync(CancellationToken cancellationToken)
{
    await slider.OnValueChangedAsAsyncEnumerable(cancellationToken)
        .ForEachAsync(value => { Debug.Log("Slider 当前值:" + value); });
}

4、UniTask与协程转换

4.1 直接 Await 协程

在示例中,我们可以直接 await 一个协程方法来等待其完成。通过这种方式,不仅能够利用 UniTask 的语法糖简化代码,还能在异步方法中直接处理协程的结果。

using System.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class AwaitCoroutine : MonoBehaviour
{
    async void Start()
    {
        // 直接 await 协程
        await CoroutineTest();
    }
    
    // 直接 await 协程并等待其完成
    IEnumerator CoroutineTest()
    {
        Debug.Log($"协程开始,当前时间: {Time.time}");
        // 等待 1 秒
        yield return new WaitForSeconds(1);
        Debug.Log($"协程结束,当前时间: {Time.time}");
    }
}

通过 await 协程,我们可以方便地将传统协程嵌入到异步方法中,无需担心协程的调度问题。

结果
在这里插入图片描述

4.2 将 UniTask 转换为协程

有时我们可能需要在传统协程中调用 UniTask 提供的功能。UniTask 提供了 ToCoroutine 扩展方法,可以将一个 UniTask 转换为协程,从而方便地在 StartCoroutine 中使用。

using System;
using System.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskToCoroutine : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(UniTaskToCoroutineTest());
    }

    // 将 UniTask 转换为协程并等待
    IEnumerator UniTaskToCoroutineTest()
    {
        Debug.Log($"UniTask 转换为协程开始,当前时间: {Time.time}");
        // 将 UniTask 延迟 1 秒转换为协程并等待
        yield return UniTask.Delay(TimeSpan.FromSeconds(1)).ToCoroutine();
        Debug.Log($"UniTask 转换为协程结束,当前时间: {Time.time}");
    }
}

这种转换方式允许开发者在协程环境中依然享受到 UniTask 高效的异步操作。

结果
在这里插入图片描述

4.3 将协程转换为 UniTask

同样地,如果希望在 UniTask 异步方法中使用已有的协程逻辑,可以将协程转换为 UniTask。通过 ToUniTask 扩展方法,我们可以方便地将协程转换为 UniTask,并通过 await 等待其完成。

using System.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class CoroutineToUniTask : MonoBehaviour
{
    async void Start()
    {
        await CoroutineToUniTaskTest();
    }

    // 将协程转换为 UniTask 并等待
    async UniTask CoroutineToUniTaskTest()
    {
        Debug.Log($"协程转换为 UniTask 开始,当前时间: {Time.time}");
        // 创建一个协程实例
        IEnumerator coroutine = CoroutineTest();
        // 将协程转换为 UniTask 并等待
        await coroutine.ToUniTask(this);
        Debug.Log($"协程转换为 UniTask 结束,当前时间: {Time.time}");
    }

    IEnumerator CoroutineTest()
    {
        Debug.Log($"协程开始,当前时间: {Time.time}");
        // 等待 1 秒
        yield return new WaitForSeconds(1);
        Debug.Log($"协程结束,当前时间: {Time.time}");
    }
}

这种转换方式让我们可以在统一的异步方法中混合使用协程和 UniTask,充分利用各自的优势,从而使代码结构更清晰。

结果
在这里插入图片描述

5、令牌复用机制

使用 UniTask 的TimeoutController进行优化,减少每次调用异步方法时用于超时的 CancellationTokenSource 的堆内存分配

TimeoutController timeoutController = new TimeoutController(); // 提前创建好,以便复用。

async UniTask FooAsync()
{
    try
    {
        // 您可以通过 timeoutController.Timeout(TimeSpan) 把超时设置传递到 cancellationToken。
        await UnityWebRequest.Get("http://foo").SendWebRequest()
            .WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
        timeoutController.Reset(); // 当 await 完成后调用 Reset(停止超时计时器,并准备下一次复用)。
    }
    catch (OperationCanceledException ex)
    {
        if (timeoutController.IsTimeout())
        {
            UnityEngine.Debug.Log("timeout");
        }
    }
}

使用new TimeoutController(CancellationToken),让超时结合其他取消源一起使用

TimeoutController timeoutController;
CancellationTokenSource clickCancelSource;

void Start()
{
    this.clickCancelSource = new CancellationTokenSource();
    this.timeoutController = new TimeoutController(clickCancelSource);
}

注意:UniTask 有.Timeout,.TimeoutWithoutException方法,但如果可以的话,尽量不要使用这些方法,请传递CancellationToken。因为.Timeout是在任务外部执行,所以无法停止超时任务。.Timeout意味着超时后忽略结果。如果您将一个CancellationToken传递给该方法,它将从任务内部执行,因此可以停止正在运行的任务。

6、异常处理策略

6.1 uppressCancellationThrow异常处理优化

将取消操作转换为布尔值判断,避免异常堆栈开销

// 传统方式(性能损耗)
// 取消异步 UniTask 方法中的行为,请手动抛出OperationCanceledException。
public async UniTask<int> FooAsync()
{
    await UniTask.Yield();
    throw new OperationCanceledException();
}

// 优化方式(推荐)
// 使用UniTask.SuppressCancellationThrow以避免抛出 OperationCanceledException 。它将返回(bool IsCanceled, T Result)而不是抛出异常。
var (isCanceled, _) = await UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow();
if (isCanceled)
{
    HandleTimeout();
}

6.2 全局异常监听

当检测到取消时,所有方法都会向上游抛出并传播OperationCanceledException。当异常(不限于OperationCanceledException)没有在异步方法中处理时,它将被传播到UniTaskScheduler.UnobservedTaskException。默认情况下,将接收到的未处理异常作为一般异常写入日志。可以使用UniTaskScheduler.UnobservedExceptionWriteLogType更改日志级别。若想对接收到未处理异常时的处理进行自定义,请为UniTaskScheduler.UnobservedTaskException设置一个委托

UniTaskScheduler.UnobservedTaskException += ex =>
{
    Debug.LogError($"未处理异常: {ex.Message}");
};

6.3 异常过滤处理

只想处理异常,忽略取消操作(让其传播到全局处理 cancellation 的地方),使用异常过滤器。

public async UniTask<int> BarAsync()
{
    try
    {
        var x = await FooAsync();
        return x * 2;
    }
    catch (Exception ex) when (!(ex is OperationCanceledException)) // 在 C# 9.0 下改成 when (ex is not OperationCanceledException) 
    {
        return -1;
    }
}

Forget 方法 UniTask提供 同步方法中调用异步方法 不想await 又不想有警告 可用Forget

7、UniTaskCompleitonSource 的使用:设置结果与取消任务

在 Unity 开发中,使用异步编程可以有效提升应用的响应速度和用户体验。UniTask 是一个专为 Unity 设计的高性能异步库,它提供了类似于 C# Task 的功能,但更加轻量级且性能优化。本文将介绍如何使用 UniTaskCompletionSource 来手动控制异步任务的完成和取消。

什么是 UniTaskCompletionSource?

UniTaskCompletionSource 是 UniTask 提供的一个工具类,允许开发者手动控制异步任务的完成、失败或取消。它类似于 .NET 中的 TaskCompletionSource,但针对 Unity 环境进行了优化。

通过 UniTaskCompletionSource,我们可以在需要的地方创建一个未完成的任务,并在适当的时机手动设置其结果或取消状态,从而实现对异步流程的精确控制。

示例:设置结果和取消任务

以下示例演示了如何使用 UniTaskCompletionSource 来创建一个可手动控制的异步任务,并通过按钮点击事件来设置任务结果或取消任务。

using UnityEngine;
using Cysharp.Threading.Tasks;
using System;

public class Lesson14_SetResultAndCancel : MonoBehaviour
{
    private UniTaskCompletionSource<string> _uniTaskCompletionSource;

    async void Start()
    {
        // 初始化任务源
        _uniTaskCompletionSource = new UniTaskCompletionSource<string>();

        try
        {
            // 等待任务完成
            string result = await _uniTaskCompletionSource.Task;
            Debug.Log("任务完成,结果为:" + result);
        }
        catch (OperationCanceledException)
        {
            Debug.Log("任务被取消");
        }
        catch (Exception ex)
        {
            Debug.LogError($"任务执行出错: {ex.Message}");
        }
    }

    void OnGUI()
    {
        // 创建设置值按钮
        if (GUI.Button(new Rect(540, 120, 120, 120), "设置值"))
        {
            // 设置任务结果
            _uniTaskCompletionSource.TrySetResult("任务执行成功!");
        }

        // 创建取消按钮
        if (GUI.Button(new Rect(540, 300, 120, 120), "取消任务"))
        {
            // 取消任务
            _uniTaskCompletionSource.TrySetCanceled();
        }
    }
}

在上述代码中:

初始化任务源:在 Start 方法中,创建了一个 UniTaskCompletionSource 实例 _uniTaskCompletionSource。

等待任务完成:使用 await _uniTaskCompletionSource.Task 来等待任务的完成。如果任务被取消,会捕获 OperationCanceledException 异常;如果发生其他异常,会进行相应的错误处理。

设置任务结果:在 OnGUI 方法中,创建了一个按钮,当点击该按钮时,调用 _uniTaskCompletionSource.TrySetResult(“任务执行成功!”) 来手动设置任务的结果。

取消任务:同样在 OnGUI 方法中,创建了另一个按钮,当点击该按钮时,调用 _uniTaskCompletionSource.TrySetCanceled() 来取消任务。

注意事项

多次设置任务状态:TrySetResult、TrySetCanceled 和 TrySetException 方法都是尝试设置任务的状态,如果任务已经完成或被取消,再次调用这些方法将不会生效。因此,确保在任务未完成时才调用这些方法。

异常处理:在等待任务的过程中,建议使用 try-catch 块来捕获可能出现的异常,特别是 OperationCanceledException,以便正确处理任务取消的情况。

线程安全:UniTaskCompletionSource 的方法是线程安全的,可以在不同的线程中调用。但在 Unity 中,通常建议在主线程中操作 UI 和游戏对象。

通过上述示例和注意事项,我们可以在 Unity 中使用 UniTaskCompletionSource 来精确控制异步任务的完成和取消,从而编写出更为灵活和高效的异步代码。

五、常见问题解答

Q: UniTask 和协程哪个更好?
A: 大多数情况下 UniTask 更好,特别是需要返回值、异常处理或组合多个异步操作时。

Q: UniTask 会产生垃圾吗?
A: 几乎不会!这是 UniTask 的最大优势之一。

Q: 可以在 WebGL 中使用吗?
A: 可以!UniTask 完全支持 WebGL。

Q: 如何调试 UniTask?
A: 使用 Unity 的普通调试方法即可,UniTask 也提供了 TaskTracker 窗口可视化查看任务状态。

六、总结

UniTask 为 Unity 带来了现代化的异步编程体验,相比传统协程和 Task 有显著优势:

  • ✅ 代码更简洁易读
  • ✅ 性能更高,几乎零GC
  • ✅ 更好的异常处理
  • ✅ 更灵活的取消机制
  • ✅ 原生支持 Unity 的各种异步操作

从今天开始尝试用 UniTask 替换你的协程和异步代码,你会发现异步编程原来可以如此简单高效!


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

<think>好的,我现在需要详细介绍一下C#async/awaitUnity中的异步使用,并且要多举几个例子来说明。首先,我得回忆一下asyncawait的基本概念,以及它们在Unity中的应用场景。 首先,asyncawaitC#中用于简化异步编程的关键字。在Unity中,异步操作非常常见,比如加载资源、网络请求、延迟执行等。以前可能用协程(Coroutine)来处理这些,但async/await提供了直观的写法,特别是对于有C#背景的开发者来说可能熟悉。 接下来,我需要明确几个重点:语法结构、在Unity中的使用注意事项、优点以及具体的例子。可能还需要对比协程async/await的区别,帮助用户理解为什么选择后者。 然后,例子部分需要涵盖不同的场景。比如加载场景、等待时间、网络请求、同时处理多个异步任务等。每个例子都要说明如何使用async/await,并且指出需要注意的地方,比如是否需要使用Unity的主线程,如何避免阻塞等。 另外,UnityC#版本的支持情况也需要考虑。Unity的版本不同,可能支持的C#版本也不同,async/await需要一定的C#版本支持,比如C# 5.0及以上。可能需要提醒用户检查他们的Unity版本是否支持,或者是否需要调整编译环境。 同时,要提到在Unity中使用async/await时,涉及到主线程的问题。Unity的API大部分需要在主线程执行,所以在异步方法中调用Unity的API时,要确保上下文是否正确。例如,在后台线程中修改GameObject的属性会引发错误,可能需要回到主线程,但使用async/await默认情况下会在主线程继续执行,这可能是一个优势。 还需要注意,Unity中的一些异步操作可能需要使用特定的Async方法,比如UnityWebRequest.SendWebRequest()返回的是一个AsyncOperation,可能需要适配为Task,以便用await。或者使用TaskCompletionSource来封装旧的异步操作为Task。 另外,CancellationToken的使用也是一个重要点,如何在Unity中处理取消异步操作,比如当场景切换或对象销毁时取消正在进行的异步任务,避免内存泄漏或错误。 现在,我需要组织这些内容,分成几个部分,每个部分配以例子。可能的结构如下: 1. 基本语法结构:如何在Unity中声明async方法,使用await。 2. 例子一:等待一段时间,比如延迟执行。 3. 例子二:异步加载资源或场景。 4. 例子三:处理多个异步任务,比如同时加载多个资源。 5. 例子四:网络请求,使用UnityWebRequest配合async/await。 6. 注意事项:线程上下文、取消操作、兼容性等。 每个例子需要代码示例,并解释关键点。比如,在例子一中,使用Task.Delay来替代协程中的WaitForSeconds。不过要注意,Task.Delay使用的是系统时间,而WaitForSeconds受游戏时间缩放影响,可能需要使用Unity的等待方式,比如使用AsyncOperation或者自定义的等待方法。 可能需要使用Unity提供的AsyncOperation的扩展方法,比如GetAwaiter(),或者使用UniTask这样的第三方库来好地集成Unity异步操作。不过用户的问题可能倾向于使用原生的async/await,而不是第三方库。 例如,UnityWebRequest.SendWebRequest()返回的是一个AsyncOperation,可以await,但可能需要配置成允许异步完成。或者使用Task.Run来包装,但需要注意线程问题。 另外,Unity中的MonoBehaviour生命周期方法是否可以标记为async?比如在Start或Update方法中使用await。根据我的知识,这应该是允许的,但需要注意如果方法被标记为async void,可能会导致异常无法捕获,所以最好使用async Task,并在调用时处理异常。 总结下来,步骤应该是: - 介绍async/await的基本用法。 - 提供多个具体例子,涵盖不同场景。 - 指出常见问题注意事项。 - 比较与协程的异同,说明各自的适用场景。 现在开始组织内容,确保每个例子正确,并符合Unity的实际应用。例如: 例子一:延迟执行。使用Task.Delay,但要注意游戏的时间缩放。或者使用自定义的等待方法,比如将秒转换为毫秒,并考虑Time.timeScale。或者使用Unity的WaitForSeconds,但如何用await来等待它?可能需要使用UniTask库,或者自己封装一个Awaiter。如果用户不想用第三方库,可能需要用Task.Delay结合Time.deltaTime来计算,但可能不够准确。 例如: public async void Start() { await Task.Delay(1000); // 等待1秒,但受系统时间影响,游戏暂停时也会继续 } 但这样可能不符合游戏暂停的需求。所以可能需要使用协程的WaitForSeconds,或者另一种方式: public async void Start() { float duration = 1f; float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; await Task.Yield(); } // 执行后续代码 } 但这样写的话,需要手动处理循环,可能不如协程方便。这时候可能需要使用UniTask库的Delay方法,或者直接使用协程。 这说明在某些情况下,原生async/await可能不如协程方便,或者需要额外处理。但用户的问题是如何用async/await,所以需要找到合适的方式。 另一个例子,加载场景: using UnityEngine.SceneManagement; using System.Threading.Tasks; public async void LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); asyncLoad.allowSceneActivation = false; while (!asyncLoad.isDone) { float progress = Mathf.Clamp01(asyncLoad.progress / 0.9f); Debug.Log("加载进度: " + (progress * 100) + "%"); if (asyncLoad.progress >= 0.9f) { asyncLoad.allowSceneActivation = true; } await Task.Yield(); } } 这里用await Task.Yield()来每帧等待,类似于协程中的yield return null。这种方法可能有效,但需要手动循环检查进度。 或者,可以封装AsyncOperation为Task: public static class AsyncOperationExtensions { public static Task AsTask(this AsyncOperation asyncOp) { var tcs = new TaskCompletionSource<bool>(); asyncOp.completed += _ => tcs.SetResult(true); return tcs.Task; } } 然后可以这样使用: public async Task LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); await asyncLoad.AsTask(); Debug.Log("场景加载完成"); } 这样简洁,但需要自己写扩展方法。这种方法可能符合async/await的风格。 例子三:同时加载多个资源,使用Task.WhenAll: public async Task LoadMultipleTextures(string[] paths) { List<Task<Texture>> tasks = new List<Task<Texture>>(); foreach (var path in paths) { tasks.Add(LoadTextureAsync(path)); } Texture[] textures = await Task.WhenAll(tasks); Debug.Log("所有贴图加载完成"); } private async Task<Texture> LoadTextureAsync(string path) { UnityWebRequest request = UnityWebRequestTexture.GetTexture(path); await request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { return DownloadHandlerTexture.GetContent(request); } else { throw new System.Exception("加载失败: " + request.error); } } 这里需要注意UnityWebRequest的SendWebRequest返回的是AsyncOperation,所以需要await,但可能需要使用扩展方法,或者直接使用.SendWebRequest().AsTask(),如果已经封装了扩展方法的话。 另一个例子,网络请求: public async Task<string> FetchDataFromServer(string url) { using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { await webRequest.SendWebRequest(); if (webRequest.result == UnityWebRequest.Result.ConnectionError || webRequest.result == UnityWebRequest.Result.ProtocolError) { Debug.LogError("错误: " + webRequest.error); return null; } else { return webRequest.downloadHandler.text; } } } 这样封装后,可以在其他方法中await这个FetchDataFromServer。 需要注意,UnityWebRequest在using语句中,确保资源释放。另外,处理错误的情况。 关于取消操作,可以使用CancellationTokenSource: private CancellationTokenSource cts; public async void StartDownload() { cts = new CancellationTokenSource(); try { string data = await FetchDataFromServer("http://example.com", cts.Token); Debug.Log(data); } catch (TaskCanceledException) { Debug.Log("下载被取消"); } } public void CancelDownload() { cts?.Cancel(); } 在FetchDataFromServer方法中需要传递CancellationToken,并在适当的时候检查是否取消: public async Task<string> FetchDataFromServer(string url, CancellationToken cancellationToken) { using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { var asyncOp = webRequest.SendWebRequest(); while (!asyncOp.isDone) { cancellationToken.ThrowIfCancellationRequested(); await Task.Yield(); } // 处理结果 } } 或者,可以将异步操作封装为Task,并在其中注册取消: public static class AsyncOperationExtensions { public static Task AsTask(this AsyncOperation asyncOp, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource<bool>(); asyncOp.completed += _ => tcs.SetResult(true); cancellationToken.Register(() => tcs.TrySetCanceled()); return tcs.Task; } } 这样,在调用时可以传递CancellationToken: await webRequest.SendWebRequest().AsTask(cancellationToken); 这样在取消时,会触发Task取消。 最后,注意事项部分: - Unity的大部分API需要在主线程调用,async/await默认会回到原上下文,因此在Unity中,await之后的代码通常会在主线程执行,这使得可以直接操作Unity对象,无需担心线程问题。 - 避免async void,除非是事件处理(如按钮点击),因为async void无法捕获异常,容易导致崩溃。应尽量使用async Task。 - 处理取消,避免内存泄漏无效操作。 - Unity版本.NET版本的支持,确保项目设置中的API Compatibility Level支持.NET 4.x或高,以便使用async/await。 - 性能考虑,频繁创建销毁Task可能带来开销,需合理使用。 总结后,整理这些内容,分步骤说明,并提供代码示例,同时指出常见问题最佳实践。</think>### C#async/awaitUnity中的异步使用详解 #### 一、基本概念与语法 `async/await`是C#中简化异步编程的关键字,通过它们可以编写直观的异步代码。在Unity中,常用于处理资源加载、网络请求、延迟操作等场景。核心规则: - `async`标记方法为异步方法。 - `await`等待异步操作完成,期间释放线程控制权。 - 返回类型为`Task`或`Task<T>`(推荐),避免`async void`。 #### 二、Unity中的使用示例 ##### 示例1:等待固定时间(受游戏时间缩放影响) ```csharp public async Task DelayAsync(float seconds) { float start = Time.time; while (Time.time - start < seconds) { await Task.Yield(); // 每帧恢复,类似协程 } Debug.Log("等待结束"); } // 调用 async void Start() { await DelayAsync(3f); Debug.Log("3秒后执行"); } ``` ##### 示例2:异步加载场景 ```csharp using UnityEngine.SceneManagement; using System.Threading.Tasks; public async Task LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); asyncLoad.allowSceneActivation = false; // 控制是否自动激活场景 while (!asyncLoad.isDone) { float progress = asyncLoad.progress / 0.9f; // Unity加载到0.9会暂停 Debug.Log($"加载进度: {progress * 100}%"); if (progress >= 1f) { asyncLoad.allowSceneActivation = true; } await Task.Yield(); // 每帧检查一次 } } ``` ##### 示例3:并行加载多个资源 ```csharp public async Task LoadAllTexturesAsync(string[] urls) { List<Task<Texture>> tasks = new List<Task<Texture>>(); foreach (var url in urls) { tasks.Add(LoadTextureAsync(url)); } Texture[] textures = await Task.WhenAll(tasks); Debug.Log($"共加载 {textures.Length} 张贴图"); } private async Task<Texture> LoadTextureAsync(string url) { using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url)) { await request.SendWebRequest(); // UnityWebRequest的异步操作 if (request.result == UnityWebRequest.Result.Success) { return DownloadHandlerTexture.GetContent(request); } else { throw new System.Exception($"加载失败: {request.error}"); } } } ``` ##### 示例4:取消异步操作 ```csharp private CancellationTokenSource _cts; public async void StartDownload() { _cts = new CancellationTokenSource(); try { string data = await FetchDataAsync("https://api.example.com", _cts.Token); Debug.Log(data); } catch (TaskCanceledException) { Debug.Log("请求被取消"); } } public void Cancel() { _cts?.Cancel(); // 触发取消 } private async Task<string> FetchDataAsync(string url, CancellationToken token) { using (UnityWebRequest request = UnityWebRequest.Get(url)) { var asyncOp = request.SendWebRequest(); token.Register(() => request.Abort()); // 取消时终止请求 await asyncOp; // 直接等待UnityWebRequest的AsyncOperation return request.downloadHandler.text; } } ``` #### 三、注意事项 1. **线程安全**:Unity API需在主线程调用,`async/await`默认在调用上下文恢复(通常是主线程)。 2. **避免阻塞**:不要在异步方法中调用`.Result`或`.Wait()`,会导致死锁。 3. **错误处理**:用`try-catch`捕获异步异常,避免未处理异常导致崩溃。 4. **性能优化**:频繁创建`Task`可能影响性能,建议复用或使用对象池。 5. **版本兼容**:需在Unity Player Settings中设置`.NET 4.x`或`.NET Standard 2.1`。 #### 四、与协程(Coroutine)的对比 | 特性 | async/await | 协程 | |--------------------|--------------------------------------|-------------------------------| | **语法简洁性** | 直观,类似同步代码 | 需要`yield return`迭代器 | | **返回值处理** | 支持`Task<T>`直接返回结果 | 需通过回调或全局变量传递 | | **取消支持** | 原生支持`CancellationToken` | 需手动判断`StopCoroutine` | | **多任务并行** | 通过`Task.WhenAll`轻松实现 | 需复杂的手动管理 | #### 五、总结 `async/await`在Unity中提供了现代化的异步编程方式,尤其适合复杂逻辑并行任务。结合`CancellationToken``Task.WhenAll`等特性,可以高效管理资源加载、网络请求等场景。但需注意Unity的API线程限制版本兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向宇it

创作不易,感谢你的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值