C# Unity 时间定时调度系统
之前的文章也有写过时间调度系统,但是没有支持异步调度只有回调调度,而且效率和代码可读性不是很好,下面介绍一种更优质的时间调度系统
1.TimerAction
首先需要定义一个时间行为,每次延时后需要干什么,延迟的时间类型是什么都需要使用TimerAction
public sealed class TimerAction : IDisposable
{private static long _id;//每个时间任务的ID生成public long Id;//每个时间任务的IDpublic long Time;//需要等待的时间(只有设置为Repeated的类型才有用)public object CallBack;//定时回调(不同类型的时间调度不同的类型可能是异步可能是委托所以用object类型)public TimerType TimerType;//时间调度类型public static TimerAction Create(){TimerAction timerAction = Pool<TimerAction>.Rent(); // 从引用池中创建时间调度任务减轻GCtimerAction.Id = _id++;return timerAction;}public void Dispose(){Id = 0;Time = 0;CallBack = null;this.TimerType = TimerType.None;Pool<TimerAction>.Return(this);}
}
2.时间调度类型TimerType
public enum TimerType
{None,OnceTimer,//单次回调委托类型的时间调度OnceWaitTimer,//单次异步时间调度(await/async)RepeatedTimer,//重复的时间调度
}
3.TimerScheduler时间调度器
这个调度器是个单例,单例在此不讲解实现可以看之前的文章有提
(1)获取当前时间
由于是在Unity环境下则使用Unity获取时间的方式,若在服务器或者纯C#的环境该时间的获取需要根据对应时间填入即可
private long GetNowTime(){return (long)Time.time * 1000;}
(2)执行一个时间调度
public long OnceTimer(long time, Action action){return OnceTillTimer(GetNowTime() + time, action);}public long OnceTillTimer(long tillTime, Action action){if (tillTime < GetNowTime())Debug.LogError($"new once time too small tillTime:{tillTime} Now:{GetNowTime()}");TimerAction timerAction = TimerAction.Create();timerAction.CallBack = action;timerAction.TimerType = TimerType.OnceTimer;AddTimer(tillTime, timerAction);return timerAction.Id;}
- 这里说明一下_minTime 这个字段用于辨别最小时间是否达到,如果连最小时间都没有达到就不执行所有时间调度的检测
private void AddTimer(long tillTime, TimerAction timerAction){_timerActionDic.Add(timerAction.Id, timerAction);_timeIdDic.Add(tillTime, timerAction.Id);if (tillTime < _minTime)_minTime = tillTime;}
(3)执行时间调度检测
private long _minTime;private readonly Queue<long> _timeOutTimeQueue = new();//tillTimeprivate readonly Queue<long> _timeOutIdsQueue = new();//idprivate readonly Dictionary<long, TimerAction> _timerActionDic = new();//Key: idprivate readonly SortedOneToManyList<long, long> _timeIdDic = new();//Key: tillTime Value: id
public void Update(){try{long currTime = GetNowTime();if (_timeIdDic.Count == 0)return;if (currTime < _minTime)return;_timeOutTimeQueue.Clear();_timeOutIdsQueue.Clear();foreach (var (key, value) in _timeIdDic){if (key > currTime){_minTime = key;break;}_timeOutTimeQueue.Enqueue(key);}while (_timeOutTimeQueue.TryDequeue(out long tillTime)){foreach (long timerId in _timeIdDic[tillTime]){_timeOutIdsQueue.Enqueue(timerId);}_timeIdDic.RemoveKey(tillTime);}while (_timeOutIdsQueue.TryDequeue(out long timerId)){if (!_timerActionDic.TryGetValue(timerId, out TimerAction timerAction))continue;_timerActionDic.Remove(timerId);switch (timerAction.TimerType){case TimerType.OnceTimer:{Action action = (Action)timerAction.CallBack;timerAction.Dispose();if (action == null){Debug.LogError("Call Back Is Null");break;}action();}break;case TimerType.RepeatedTimer:{Action action = (Action)timerAction.CallBack;AddTimer(GetNowTime() + timerAction.Time, timerAction);if (action == null){Debug.LogError("Call Back Is Null");break;}action();}break;case TimerType.OnceWaitTimer:{TaskCompletionSource<bool> taskCompletionSource = (TaskCompletionSource<bool>)timerAction.CallBack;timerAction.Dispose();taskCompletionSource.SetResult(true);}break;default:break;}}}catch (Exception ex){Debug.LogError(ex.Message);}}
(4)TimerScheduler完整代码
public class TimerScheduler : Singleton<TimerScheduler>, IUpdateSingleton
{private long _minTime;private readonly Queue<long> _timeOutTimeQueue = new();//tillTimeprivate readonly Queue<long> _timeOutIdsQueue = new();//idprivate readonly Dictionary<long, TimerAction> _timerActionDic = new();//Key: idprivate readonly SortedOneToManyList<long, long> _timeIdDic = new();//Key: tillTime Value: idprivate long GetNowTime(){return (long)Time.time * 1000;}private void AddTimer(long tillTime, TimerAction timerAction){_timerActionDic.Add(timerAction.Id, timerAction);_timeIdDic.Add(tillTime, timerAction.Id);if (tillTime < _minTime)_minTime = tillTime;}public long OnceTimer(long time, Action action){return OnceTillTimer(GetNowTime() + time, action);}public long OnceTillTimer(long tillTime, Action action){if (tillTime < GetNowTime())Debug.LogError($"new once time too small tillTime:{tillTime} Now:{GetNowTime()}");TimerAction timerAction = TimerAction.Create();timerAction.CallBack = action;timerAction.TimerType = TimerType.OnceTimer;AddTimer(tillTime, timerAction);return timerAction.Id;}public long RepeatedTimer(long time,Action action){if (time <= 0){throw new Exception("repeated time <= 0");}long tillTime = GetNowTime() + time;TimerAction timerAction = TimerAction.Create();timerAction.CallBack = action;timerAction.TimerType = TimerType.RepeatedTimer;timerAction.Time = time;AddTimer(tillTime, timerAction);return timerAction.Id;}public void Remove(long timerId){if (!_timerActionDic.Remove(timerId, out TimerAction timerAction))return;timerAction?.Dispose();}public void Update(){try{long currTime = GetNowTime();if (_timeIdDic.Count == 0)return;if (currTime < _minTime)return;_timeOutTimeQueue.Clear();_timeOutIdsQueue.Clear();foreach (var (key, value) in _timeIdDic){if (key > currTime){_minTime = key;break;}_timeOutTimeQueue.Enqueue(key);}while (_timeOutTimeQueue.TryDequeue(out long tillTime)){foreach (long timerId in _timeIdDic[tillTime]){_timeOutIdsQueue.Enqueue(timerId);}_timeIdDic.RemoveKey(tillTime);}while (_timeOutIdsQueue.TryDequeue(out long timerId)){if (!_timerActionDic.TryGetValue(timerId, out TimerAction timerAction))continue;_timerActionDic.Remove(timerId);switch (timerAction.TimerType){case TimerType.OnceTimer:{Action action = (Action)timerAction.CallBack;timerAction.Dispose();if (action == null){Debug.LogError("Call Back Is Null");break;}action();}break;case TimerType.RepeatedTimer:{Action action = (Action)timerAction.CallBack;AddTimer(GetNowTime() + timerAction.Time, timerAction);if (action == null){Debug.LogError("Call Back Is Null");break;}action();}break;case TimerType.OnceWaitTimer:{TaskCompletionSource<bool> taskCompletionSource = (TaskCompletionSource<bool>)timerAction.CallBack;timerAction.Dispose();taskCompletionSource.SetResult(true);}break;default:break;}}}catch (Exception ex){Debug.LogError(ex.Message);}}public async Task<bool> WaitAsync(long time, CancellationAction cancellationAction = null){return await WaitTillAsync(GetNowTime() + time, cancellationAction);}public async Task<bool> WaitTillAsync(long tillTime, CancellationAction cancellationAction = null){if (GetNowTime() > tillTime)return true;TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();TimerAction timerAction = TimerAction.Create();long timerId = timerAction.Id;timerAction.CallBack = taskCompletionSource;timerAction.TimerType = TimerType.OnceWaitTimer;void CancelAction(){if (!_timerActionDic.ContainsKey(timerId))return;Remove(timerId);taskCompletionSource.SetResult(false);}bool b;try{cancellationAction?.Add(CancelAction);AddTimer(tillTime, timerAction);b = await taskCompletionSource.Task;}catch(Exception ex){Debug.LogError(ex.Message);return true;}return b;}protected override void Load(int assemblyName){}protected override void UnLoad(int assemblyName){}
}
4.测试
- 提供一下CancellationAction代码实现,用于一切的取消事件(非时间调度器专属,任何取消操作都可参考)
public sealed class CancellationAction
{private HashSet<Action> _actions = new HashSet<Action>();public bool IsCanel => _actions == null;public void Add(Action action){_actions.Add(action);}public void Remove(Action action){_actions.Remove(action);}public void Cancel(){if (_actions == null)return;foreach (Action action in _actions){try{action?.Invoke();}catch (Exception e){Debug.LogError(e.Message);}}_actions.Clear();_actions = null;}
}
public class TestTimer : MonoBehaviour
{private long repeatedId;private CancellationAction timerCancelAction = new CancellationAction();async void Start(){SingletonSystem.Initialize();AssemblyManager.Initialize();TimerScheduler.Instance.OnceTimer(5000, () =>{Debug.Log("第一个5s后了!!!");});TimerScheduler.Instance.OnceTimer(5000, () =>{Debug.Log("第二个5s后了!!!");});TimerScheduler.Instance.OnceTimer(6000, () =>{Debug.Log("6s后了!!!");});repeatedId = TimerScheduler.Instance.RepeatedTimer(2000, () =>{Debug.Log("每两秒重复运行!!!");});await TimerScheduler.Instance.WaitAsync(3000);Debug.Log("这是第一个3s以后");await TimerScheduler.Instance.WaitAsync(3000);Debug.Log("这是第二个3s以后");bool isCanel = await TimerScheduler.Instance.WaitAsync(5000, timerCancelAction);if (!isCanel){Debug.Log("取消定时");}else{Debug.Log("五秒后执行!!!");}}void Update(){SingletonSystem.Update();if (Input.GetKeyDown(KeyCode.P)){if (!timerCancelAction.IsCanel){timerCancelAction.Cancel();}}}
}