4.UniTask等待操作

4.等待操作


4.1 知识点

UniTask 等待操作详解

在 Unity 开发中,处理异步操作时,常需要等待特定条件满足后再继续执行后续代码。UniTask 提供了高效的等待方法,如 UniTask.WaitUntilUniTask.WaitUntilValueChanged,使得异步代码的编写更加简洁和高效。本文将通过具体示例,详细介绍这些等待操作的用法和注意事项。

UniTask.WaitUntil:等待条件满足

UniTask.WaitUntil 用于等待直到指定的条件返回 true,然后继续执行后续代码。

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

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

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

    async UniTaskVoid WaitUntilPositionExceeds(CancellationToken token)
    {
        Debug.Log($"等待开始: {Time.time}");
        await UniTask.WaitUntil(() => ball.transform.position.x > 1, cancellationToken: token);
        Debug.Log($"等待结束: {Time.time}");
        ball.GetComponent<Renderer>().material.color = Color.red;
    }

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

在上述示例中,WaitUntilPositionExceeds 方法会等待直到 ball 的 x 坐标大于 1,然后将其颜色更改为红色。UniTask.WaitUntil 方法接受一个返回布尔值的委托,当该委托返回 true 时,等待结束,继续执行后续代码。

UniTask.WaitUntilValueChanged:等待值发生变化

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

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

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

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

    async UniTaskVoid WaitUntilPositionChanges(CancellationToken token)
    {
        await UniTask.WaitUntilValueChanged(ball.transform, x => x.position, cancellationToken: token);
        Debug.Log("小球位置已变化");
    }

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

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

使用 CancellationToken 取消等待操作

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

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

public class CancellationExample : MonoBehaviour
{
    private CancellationTokenSource _cancellationTokenSource;

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

    async UniTaskVoid PerformCancelableTask(CancellationToken token)
    {
        try
        {
            await UniTask.Delay(5000, cancellationToken: token);
            Debug.Log("任务完成");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("任务被取消");
        }
    }

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

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

进行测试

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

/// <summary>
/// 使用 UniTask 实现等待操作的示例
/// </summary>
public class Lesson04_等待操作 : MonoBehaviour
{
    public GameObject ball; // 要操作的小球对象
    private CancellationTokenSource _cancellationTokenSource; // 用于取消异步任务的令牌源
    private Vector3 _moveDirection = Vector3.right; // 小球移动方向,初始向右

    void Start()
    {
        // 初始化令牌源
        _cancellationTokenSource = new CancellationTokenSource();
        // 启动等待小球移动超过指定位置的任务
        WaitUntilPositionExceeds(_cancellationTokenSource.Token).Forget();
        // 启动等待小球位置变化的任务
        WaitUntilPositionChanges(_cancellationTokenSource.Token).Forget();
    }

    void Update()
    {
        // 按当前移动方向移动小球
        ball.transform.Translate(_moveDirection * Time.deltaTime);
        // 当小球移动到 x > 2 时,改变方向向左
        if (ball.transform.position.x > 2)
        {
            _moveDirection = Vector3.left;
        }
        // 当小球移动到 x < -2 时,改变方向向右
        else if (ball.transform.position.x < -2)
        {
            _moveDirection = Vector3.right;
        }
    }

    /// <summary>
    /// 等待小球的 x 坐标超过 1,然后改变其颜色
    /// </summary>
    /// <param name="token">取消令牌</param>
    async UniTaskVoid WaitUntilPositionExceeds(CancellationToken token)
    {
        Debug.Log("等待开始: " + Time.time);
        // 等待直到小球的 x 坐标超过 1
        await UniTask.WaitUntil(() => ball.transform.position.x > 1, cancellationToken: token);
        Debug.Log("等待结束: " + Time.time);
        // 将小球颜色改为红色
        ball.GetComponent<Renderer>().material.color = Color.red;
    }

    /// <summary>
    /// 等待小球的位置发生变化
    /// </summary>
    /// <param name="token">取消令牌</param>
    async UniTaskVoid WaitUntilPositionChanges(CancellationToken token)
    {
        // 等待直到小球的位置发生变化
        await UniTask.WaitUntilValueChanged(ball.transform, x => x.position, cancellationToken: token);
        Debug.Log("小球位置已变化");
    }

    private void OnDestroy()
    {
        // 在对象销毁时取消所有异步任务
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}

总结

UniTask 提供了丰富的等待操作方法,如 UniTask.WaitUntilUniTask.WaitUntilValueChanged,能够高效地替代传统的协程,实现更简洁和性能优化的异步代码。在使用这些方法时,结合 CancellationToken 可以更好地控制异步任务的生命周期,避免不必要的资源消耗。


4.2 知识点代码

Lesson04_等待操作.cs

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

/// <summary>
/// 使用 UniTask 实现等待操作的示例
/// </summary>
public class Lesson04_等待操作 : MonoBehaviour
{
    public GameObject ball; // 要操作的小球对象
    private CancellationTokenSource _cancellationTokenSource; // 用于取消异步任务的令牌源
    private Vector3 _moveDirection = Vector3.right; // 小球移动方向,初始向右

    void Start()
    {
        // 初始化令牌源
        _cancellationTokenSource = new CancellationTokenSource();
        // 启动等待小球移动超过指定位置的任务
        WaitUntilPositionExceeds(_cancellationTokenSource.Token).Forget();
        // 启动等待小球位置变化的任务
        WaitUntilPositionChanges(_cancellationTokenSource.Token).Forget();
    }

    void Update()
    {
        // 按当前移动方向移动小球
        ball.transform.Translate(_moveDirection * Time.deltaTime);
        // 当小球移动到 x > 2 时,改变方向向左
        if (ball.transform.position.x > 2)
        {
            _moveDirection = Vector3.left;
        }
        // 当小球移动到 x < -2 时,改变方向向右
        else if (ball.transform.position.x < -2)
        {
            _moveDirection = Vector3.right;
        }
    }

    /// <summary>
    /// 等待小球的 x 坐标超过 1,然后改变其颜色
    /// </summary>
    /// <param name="token">取消令牌</param>
    async UniTaskVoid WaitUntilPositionExceeds(CancellationToken token)
    {
        Debug.Log("等待开始: " + Time.time);
        // 等待直到小球的 x 坐标超过 1
        await UniTask.WaitUntil(() => ball.transform.position.x > 1, cancellationToken: token);
        Debug.Log("等待结束: " + Time.time);
        // 将小球颜色改为红色
        ball.GetComponent<Renderer>().material.color = Color.red;
    }

    /// <summary>
    /// 等待小球的位置发生变化
    /// </summary>
    /// <param name="token">取消令牌</param>
    async UniTaskVoid WaitUntilPositionChanges(CancellationToken token)
    {
        // 等待直到小球的位置发生变化
        await UniTask.WaitUntilValueChanged(ball.transform, x => x.position, cancellationToken: token);
        Debug.Log("小球位置已变化");
    }

    private void OnDestroy()
    {
        // 在对象销毁时取消所有异步任务
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏