UniTask地址:GitHub – Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
功能:令一个方法在n秒后被调用。
特点:一行语句即可实现延迟调用,支持Action的多参数传入,适当操作可避免产生GC。
示例:三秒后死亡。
void Start()
{
Debug.Log($"[{Time.time}]>>调用时间");
Ib_Async.DelayDoSomethingUniTask(3,Die,this.GetCancellationTokenOnDestroy());
}
void Die()
{
Debug.Log($"[{Time.time}]>>执行时间 => 我死了");
}
输出结果:

与其他延迟方法对比(每帧调用500次延迟方法):
1、基于UniTask的Ib_Async。可以看到每帧调用了Ib_Async.DelayDoSomethingUniTask500次,耗时2.79ms左右,没有GC的产生。但是在IncreaseGold方法转换成Action<int>的时候产生了62.5KB的GC,为了避免这部分产生的GC我们可以在Start里提前将IncreaseGold转换成Action<int>。
public int gold = 0;
void IncreaseGold(int cnt)
{
gold += cnt;
}
private void Update()
{
Test_DelayDoSomethingUnitask();
}
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
Ib_Async.DelayDoSomethingUniTask(3,IncreaseGold,1,this.GetCancellationTokenOnDestroy());
}
}

简单修改后,避免了频繁创建Action<int>,可以看到每帧调用500次后没有GC产生。
public int gold = 0;
void IncreaseGold(int cnt)
{
gold += cnt;
}
private Action<int> increaseGoldAction;
void Start()
{
increaseGoldAction = IncreaseGold;
}
private void Update()
{
Test_DelayDoSomethingUnitask();
}
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
Ib_Async.DelayDoSomethingUniTask(3,increaseGoldAction,1,CancellationToken.None);
}
}

2、通过协程来延迟调用(协程也同样可以实现通用的延迟方法,这里就简单实现了)。可以看出每帧耗时仅1.56ms左右,比UniTask快上了一些,但是产生了44.9KB的GC,因为每次启动协程以及yield return new WaitForSeconds(delay);的时候都会进行内存分配。我们可以复用WaitForSeconds来避免一部分的GC。
public int gold = 0;
void IncreaseGold(int cnt)
{
gold += cnt;
}
private Action<int> increaseGoldAction;
void Start()
{
increaseGoldAction = IncreaseGold;
}
private void Update()
{
Test_DelayDoSomethingUnitask();
}
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
StartCoroutine(DelayDoSomething(3, increaseGoldAction, 1));
// Ib_Async.DelayDoSomethingUniTask(3,increaseGoldAction,1,CancellationToken.None);
}
}
IEnumerator DelayDoSomething(float delay,Action<int> action,int arg1)
{
yield return new WaitForSeconds(delay);
action?.Invoke(arg1);
}

缓存WaitForSeconds,避免频繁创建产生GC。这次的GC减少到了39.1KB,但是开启协程的开销无法避免。
public int gold = 0;
void IncreaseGold(int cnt)
{
gold += cnt;
}
private Action<int> increaseGoldAction;
void Start()
{
increaseGoldAction = IncreaseGold;
}
private void Update()
{
Test_DelayDoSomethingUnitask();
}
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
StartCoroutine(DelayDoSomething(3, increaseGoldAction, 1));
// Ib_Async.DelayDoSomethingUniTask(3,increaseGoldAction,1,CancellationToken.None);
}
}
Dictionary<float,WaitForSeconds> waitForSecondsDict = new Dictionary<float,WaitForSeconds>();
//缓存WaitForSeconds
private WaitForSeconds TryGetWaitForSeconds(float delay)
{
float key = Mathf.Round(delay * 1000f) / 1000f;
if (waitForSecondsDict.TryGetValue(key, out WaitForSeconds wait)) return wait;
wait = new WaitForSeconds(key);
waitForSecondsDict[key] = wait;
return wait;
}
IEnumerator DelayDoSomething(float delay,Action<int> action,int arg1)
{
yield return TryGetWaitForSeconds(delay);
action?.Invoke(arg1);
}

结论:经过每帧500次的调用测试,可以看出基于UniTask的Ib_Async虽然相比协程cpu耗时略高,但是在GC上有巨大的优势,在频繁调用的场景下性能稳定性更佳,这一点额外的耗时是值得的。
注意事项:
尽量避免()=>{};闭包,如下捕获了temp变量产生了78.1KB的GC:
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
int temp = i;
Ib_Async.DelayDoSomethingUniTask(3,
() => {
increaseGoldAction?.Invoke(temp);
}
,CancellationToken.None);
}
}

解决方法:使用Ib_Async的传入参数替代。78.1KB => 0B
void Test_DelayDoSomethingUnitask()
{
for (int i = 0; i < 500; i++)
{
int temp = i;
Ib_Async.DelayDoSomethingUniTask(3,increaseGoldAction,temp,CancellationToken.None);
}
}

无参数延迟方法的主要实现:
通过UniTask来异步等待。
public static class Ib_Async
{
/// <summary>
/// 无参数的延迟方法,若action带参数,请使用多参数的延迟方法,避免使用闭包()=>{};
/// </summary>
/// <param name="delay">延迟时间</param>
/// <param name="action">延迟后执行的方法</param>
/// <param name="cts">Unitask取消令牌</param>
/// <param name="cancelCallBack">取消后的回调</param>
public static void DelayDoSomethingUniTask(float delay, Action action, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, action, cts, cancelCallBack).Forget();
else Debug.Log("[Action] to delay is Null");
}
private static async UniTaskVoid DealDelayDoSomethingUniTaskCore(float delay, Action action, CancellationToken cts, Action cancelCallBack)
{
try
{
if (delay > 0)
{
await UniTask.Delay(TimeSpan.FromSeconds(delay), cancellationToken: cts);
}
else
{
await UniTask.Yield(PlayerLoopTiming.Update, cts);
}
action?.Invoke();
}
catch (OperationCanceledException)//UniTask被取消时的回调
{
cancelCallBack?.Invoke();
Debug.Log("DelayDoSomethingActionUniTask was canceled.");
}
}
}
Ib_Async源码:支持Action至多五个传入参数的重载,避免()=>{}闭包产生的GC。如有需求可自行拓展更多的传入参数。
namespace Ib_Core
{
using System.Threading;
using Cysharp.Threading.Tasks;
using System;
#region 值类型Action封装
internal interface IActionInvoker
{
void Invoke();
}
internal readonly struct ActionInvoker : IActionInvoker
{
private readonly Action _action;
public ActionInvoker(Action action)
{
_action = action;
}
public void Invoke() => _action?.Invoke();
}
internal readonly struct ActionInvoker<T1> : IActionInvoker
{
private readonly Action<T1> _action;
private readonly T1 _arg1;
public ActionInvoker(Action<T1> action, T1 arg1)
{
_action = action;
_arg1 = arg1;
}
public void Invoke() => _action?.Invoke(_arg1);
}
internal readonly struct ActionInvoker<T1, T2> : IActionInvoker
{
private readonly Action<T1, T2> _action;
private readonly T1 _arg1;
private readonly T2 _arg2;
public ActionInvoker(Action<T1, T2> action, T1 arg1, T2 arg2)
{
_action = action;
_arg1 = arg1;
_arg2 = arg2;
}
public void Invoke() => _action?.Invoke(_arg1, _arg2);
}
internal readonly struct ActionInvoker<T1, T2, T3> : IActionInvoker
{
private readonly Action<T1, T2, T3> _action;
private readonly T1 _arg1;
private readonly T2 _arg2;
private readonly T3 _arg3;
public ActionInvoker(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
{
_action = action;
_arg1 = arg1;
_arg2 = arg2;
_arg3 = arg3;
}
public void Invoke() => _action?.Invoke(_arg1, _arg2, _arg3);
}
internal readonly struct ActionInvoker<T1, T2, T3, T4> : IActionInvoker
{
private readonly Action<T1, T2, T3, T4> _action;
private readonly T1 _arg1;
private readonly T2 _arg2;
private readonly T3 _arg3;
private readonly T4 _arg4;
public ActionInvoker(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
_action = action;
_arg1 = arg1;
_arg2 = arg2;
_arg3 = arg3;
_arg4 = arg4;
}
public void Invoke() => _action?.Invoke(_arg1, _arg2, _arg3, _arg4);
}
internal readonly struct ActionInvoker<T1, T2, T3, T4, T5> : IActionInvoker
{
private readonly Action<T1, T2, T3, T4, T5> _action;
private readonly T1 _arg1;
private readonly T2 _arg2;
private readonly T3 _arg3;
private readonly T4 _arg4;
private readonly T5 _arg5;
public ActionInvoker(Action<T1, T2, T3, T4, T5> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
_action = action;
_arg1 = arg1;
_arg2 = arg2;
_arg3 = arg3;
_arg4 = arg4;
_arg5 = arg5;
}
public void Invoke() => _action?.Invoke(_arg1, _arg2, _arg3, _arg4, _arg5);
}
#endregion
/// <summary>
/// 负责异步调用,支持延迟行为
/// </summary>
public static class Ib_Async
{
#region 延迟方法与重载
/// <summary>
/// 无参数的延迟方法,若action带参数,请使用多参数的延迟方法,避免使用闭包()=>{};
/// </summary>
/// <param name="delay">延迟时间</param>
/// <param name="action">延迟后执行的方法</param>
/// <param name="cts">Unitask取消令牌</param>
/// <param name="cancelCallBack">取消后的回调</param>
public static void DelayDoSomethingUniTask(float delay, Action action, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker(action), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
/// <summary>
/// 无参数的延迟方法,延迟1帧,若action带参数,请使用多参数的延迟方法,避免使用闭包()=>{};
/// </summary>
/// <param name="action">延迟后执行的方法</param>
/// <param name="cts">Unitask取消令牌</param>
/// <param name="cancelCallBack">取消后的回调</param>
public static void DelayDoSomethingUniTask(Action action, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(0, new ActionInvoker(action), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
public static void DelayDoSomethingUniTask<T1>(float delay, Action<T1> action, T1 arg1, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker<T1>(action, arg1), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
public static void DelayDoSomethingUniTask<T1, T2>(float delay, Action<T1, T2> action, T1 arg1, T2 arg2, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker<T1, T2>(action, arg1, arg2), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
public static void DelayDoSomethingUniTask<T1, T2, T3>(float delay, Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker<T1, T2, T3>(action, arg1, arg2, arg3), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
public static void DelayDoSomethingUniTask<T1, T2, T3, T4>(float delay, Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker<T1, T2, T3, T4>(action, arg1, arg2, arg3, arg4), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
public static void DelayDoSomethingUniTask<T1, T2, T3, T4, T5>(float delay, Action<T1, T2, T3, T4, T5> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken cts, Action cancelCallBack = null)
{
if (action != null) DealDelayDoSomethingUniTaskCore(delay, new ActionInvoker<T1, T2, T3, T4, T5>(action, arg1, arg2, arg3, arg4, arg5), cts, cancelCallBack).Forget();
else Ib_Log.Warning("[Action] to delay is Null");
}
#endregion
#region 延迟核心方法
private static async UniTaskVoid DealDelayDoSomethingUniTaskCore<TInvoker>(float delay, TInvoker invoker, CancellationToken cts, Action cancelCallBack)
where TInvoker : struct, IActionInvoker
{
try
{
if (delay > 0)
{
await UniTask.Delay(TimeSpan.FromSeconds(delay), cancellationToken: cts);
}
else
{
await UniTask.Yield(PlayerLoopTiming.Update, cts);
}
invoker.Invoke();
}
catch (OperationCanceledException)
{
cancelCallBack?.Invoke();
Ib_Log.Warning("DelayDoSomethingActionUniTask was canceled.");
}
}
#endregion
}
}
评论(2)