UniTask地址:Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
功能:创建一个与Update()或FixedUpdate()类似,但更新频率和生命周期可以自定义的循环任务。
特点: 自定义更新频率与生命周期。不用在Update()中反复实现计时器。
*若有大量的异步方法同时在执行,会导致严重的性能问题。因此该方法不建议大量同时使用,不可代替Update()方法。
示例: 每0.2s恢复1点生命值。
//取消令牌,用来管理回血任务的生命周期
private CancellationTokenSource hpRegnCts;
void Start()
{
//启用一个更新周期为0.2s,每次回复1点生命值的循环任务。包含任务开始与结束的回调。
Ib_Async.CreateUpdateTask(HpRegn,0.2f,Ib_Async.ResetCts(ref hpRegnCts).Token);
}
void OnDestroy()
{
//结束循环任务
Ib_Async.ClearCts(ref hpRegnCts);
}
public int Hp = 0;
//回血1点
private void HpRegn()
{
Hp += 1;
Ib_Log.Info($"[{Time.time}]>>Hp增加了1点,当前Hp为{Hp}");
}

可以通过取消令牌随时结束循环任务,如下使用Ib_Async.DelayDoSomethingUniTask延迟调用方法在3秒后结束回血任务,自由控制任务的生命周期。还可以在创建循环任务时添加任务开始与结束的调用。
//取消令牌,用来管理回血任务的生命周期
private CancellationTokenSource hpRegnCts;
void Start()
{
//启用一个更新周期为0.2s,每次回复1点生命值的循环任务。包含任务开始与结束的回调。
Ib_Async.CreateUpdateTask(HpRegn,0.2f,Ib_Async.ResetCts(ref hpRegnCts).Token,true,OnEnterHpRegn,OnExitHpRegn);
//在3秒后取消回血任务
Ib_Async.DelayDoSomethingUniTask(3,EndHpRegn,this.GetCancellationTokenOnDestroy());
}
public int Hp = 0;
//回血1点
private void HpRegn()
{
Hp += 1;
Ib_Log.Info($"[{Time.time}]>>Hp增加了1点,当前Hp为{Hp}");
}
//结束回血
private void EndHpRegn()
{
Ib_Async.ClearCts(ref hpRegnCts);
}
//循环任务的开始与结束的回调
private void OnEnterHpRegn() => Ib_Log.Info($"[{Time.time}]>>开始回血");
private void OnExitHpRegn() => Ib_Log.Info($"[{Time.time}]>>结束回血");

不完美之处:0.2秒的间隔是不太准确的,如上3秒内只调用了14次,理论上应该是15次,有一定的精度偏差。
源码:
namespace Ib_Core
{
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
public static partial class Ib_Async
{
/// <summary>
/// 重置令牌
/// </summary>
/// <param name="cts">取消令牌</param>
/// <returns></returns>
public static CancellationTokenSource ResetCts(ref CancellationTokenSource cts)
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
}
cts = new();
return cts;
}
/// <summary>
/// 清除
/// </summary>
/// <param name="cts">取消令牌</param>
/// <returns></returns>
public static void ClearCts(ref CancellationTokenSource cts)
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
}
cts = null;
}
}
/// <summary>
/// 基于Unitask的异步Update申请方法,支持自定义更新频率。通过令牌控制生命周期
/// </summary>
public static partial class Ib_Async
{
#region 循环任务创建调用
/// <summary>
/// 创建一个在 Update 循环中异步执行的任务
/// </summary>
/// <param name="action">要执行的操作</param>
/// <param name="updateInterval">更新间隔(秒),0 表示每帧执行</param>
/// <param name="cts">取消令牌</param>
/// <param name="ignoreTimeScale">是否忽略时间缩放</param>
/// <param name="onBegin">任务开始时的回调</param>
/// <param name="onEnd">任务结束时的回调(无论正常结束还是取消都会调用)</param>
public static void CreateUpdateTask(Action action, float updateInterval, CancellationToken cts, bool ignoreTimeScale = true, Action onBegin = null, Action onEnd = null)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (updateInterval < 0) throw new ArgumentOutOfRangeException(nameof(updateInterval), "更新间隔不能为负数");
DealUpdateTask(action, Mathf.Max(0, updateInterval), ignoreTimeScale ? DelayType.UnscaledDeltaTime : DelayType.DeltaTime, PlayerLoopTiming.Update, cts, onBegin, onEnd)
.Forget();
}
/// <summary>
/// 创建一个在 FixedUpdate 循环中异步执行的任务
/// </summary>
/// <param name="action">要执行的操作</param>
/// <param name="updateInterval">更新间隔(秒),0 表示每帧执行</param>
/// <param name="cts">取消令牌</param>
/// <param name="onBegin">任务开始时的回调</param>
/// <param name="onEnd">任务结束时的回调(无论正常结束还是取消都会调用)</param>
public static void CreateFixedUpdateTask(Action action, float updateInterval, CancellationToken cts, Action onBegin = null, Action onEnd = null)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (updateInterval < 0) throw new ArgumentOutOfRangeException(nameof(updateInterval), "更新间隔不能为负数");
DealUpdateTask(action, Mathf.Max(0, updateInterval), DelayType.DeltaTime, PlayerLoopTiming.FixedUpdate, cts, onBegin, onEnd).Forget();
}
#endregion
#region 核心循环调用执行方法
private static async UniTaskVoid DealUpdateTask(Action action, float updateInterval, DelayType delayType, PlayerLoopTiming loopTiming, CancellationToken cts,
Action onBegin = null, Action onEnd = null)
{
try
{
onBegin?.Invoke();
if (updateInterval > 0)
{
var interval = TimeSpan.FromSeconds(updateInterval);
while (!cts.IsCancellationRequested)
{
SafeInvokeAction(action);
await UniTask.Delay(interval, delayType, loopTiming, cts);
}
}
else
{
while (!cts.IsCancellationRequested)
{
SafeInvokeAction(action);
await UniTask.Yield(loopTiming, cts);
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception ex) // 捕获其他所有异常
{
Ib_Log.Error(ex);
}
finally
{
onEnd?.Invoke(); // 确保 onEnd 总是被调用
}
}
private static void SafeInvokeAction(Action action)
{
try
{
action?.Invoke();
}
catch (Exception ex)
{
Ib_Log.Error(ex);
}
}
#endregion
}
}
评论(1)