Unity:基于C#的定时回调系统(可用于客户端和服务端)

本文是学习Siki学院Plane老师的《定时回调系统技术专题》视频课程的学习笔记和总结

实现功能

  1. 支持时间定时,帧定时
  2. 支持任务可循环,可取消,可替换
  3. 使用简单,调用方便

思路:

  • 如何扩展定时任务:将时间计时转为帧数计时
  • 如何扩展取消/替换定时任务:生成唯一id,通过id索引操作任务
  • 如何扩展循环定时任务:通过任务计数运算
  • 如何扩展时间单位支持:统一换算为最小的毫秒运算
  • 如何支持多线程定时任务:通过临时列表进行缓存,错开操作时间;避免使用锁,提升操作效率
  • 如何实现基础定时任务:通过Update()来检测任务条件

 需要注意的问题:

  • 多线程中的线程数据安全问题

 核心代码:

using System;
using System.Collections.Generic;
using System.Timers;

public class PETimer {
    private Action taskLog;
    private Action, int> taskHandle;
    private static readonly string lockTid = "lockTid";
    private DateTime startDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
    private double nowTime;
    private Timer srvTimer;
    private int tid;
    private List tidLst = new List();
    private List recTidLst = new List();

    private static readonly string lockTime = "lockTime";
    private List tmpTimeLst = new List();
    private List taskTimeLst = new List();
    private List tmpDelTimeLst = new List();

    private int frameCounter;

    private static readonly string lockFrame = "lockFrame";
    private List tmpFrameLst = new List();
    private List taskFrameLst = new List();
    private List tmpDelFrameLst = new List();

    public PETimer(int interval = 0) {
        tidLst.Clear();
        recTidLst.Clear();

        tmpTimeLst.Clear();
        taskTimeLst.Clear();

        tmpFrameLst.Clear();
        taskFrameLst.Clear();

        if (interval != 0) {
            srvTimer = new Timer(interval) {
                AutoReset = true
            };

            srvTimer.Elapsed += (object sender, ElapsedEventArgs args) => {
                Update();
            };
            srvTimer.Start();
        }
    }

    public void Update() {
        CheckTimeTask();
        CheckFrameTask();

        DelTimeTask();
        DelFrameTask();

        if (recTidLst.Count > 0) {
            lock (lockTid) {
                RecycleTid();
            }
        }
    }
    private void DelTimeTask() {
        if (tmpDelTimeLst.Count > 0) {
            lock (lockTime) {
                for (int i = 0; i < tmpDelTimeLst.Count; i++) {
                    bool isDel = false;
                    int delTid = tmpDelTimeLst[i];
                    for (int j = 0; j < taskTimeLst.Count; j++) {
                        PETimeTask task = taskTimeLst[j];
                        if (task.tid == delTid) {
                            isDel = true;
                            taskTimeLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            //LogInfo("Del taskTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                            break;
                        }
                    }

                    if (isDel)
                        continue;

                    for (int j = 0; j < tmpTimeLst.Count; j++) {
                        PETimeTask task = tmpTimeLst[j];
                        if (task.tid == delTid) {
                            tmpTimeLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            //LogInfo("Del tmpTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                            break;
                        }
                    }
                }
            }
        }
    }
    private void DelFrameTask() {
        if (tmpDelFrameLst.Count > 0) {
            lock (lockFrame) {
                for (int i = 0; i < tmpDelFrameLst.Count; i++) {
                    bool isDel = false;
                    int delTid = tmpDelFrameLst[i];
                    for (int j = 0; j < taskFrameLst.Count; j++) {
                        PEFrameTask task = taskFrameLst[j];
                        if (task.tid == delTid) {
                            isDel = true;
                            taskFrameLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            break;
                        }
                    }

                    if (isDel)
                        continue;

                    for (int j = 0; j < tmpFrameLst.Count; j++) {
                        PEFrameTask task = tmpFrameLst[j];
                        if (task.tid == delTid) {
                            tmpFrameLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            break;
                        }
                    }
                }
            }
        }
    }
    private void CheckTimeTask() {
        if (tmpTimeLst.Count > 0) {
            lock (lockTime) {
                //加入缓存区中的定时任务
                for (int tmpIndex = 0; tmpIndex < tmpTimeLst.Count; tmpIndex++) {
                    taskTimeLst.Add(tmpTimeLst[tmpIndex]);
                }
                tmpTimeLst.Clear();
            }
        }

        //遍历检测任务是否达到条件
        nowTime = GetUTCMilliseconds();
        for (int index = 0; index < taskTimeLst.Count; index++) {
            PETimeTask task = taskTimeLst[index];
            if (nowTime.CompareTo(task.destTime) < 0) {
                continue;
            }
            else {
                Action cb = task.callback;
                try {
                    if (taskHandle != null) {
                        taskHandle(cb, task.tid);
                    }
                    else {
                        if (cb != null) {
                            cb(task.tid);
                        }
                    }
                }
                catch (Exception e) {
                    LogInfo(e.ToString());
                }

                //移除已经完成的任务
                if (task.count == 1) {
                    taskTimeLst.RemoveAt(index);
                    index--;
                    recTidLst.Add(task.tid);
                }
                else {
                    if (task.count != 0) {
                        task.count -= 1;
                    }
                    task.destTime += task.delay;
                }
            }
        }
    }
    private void CheckFrameTask() {
        if (tmpFrameLst.Count > 0) {
            lock (lockFrame) {
                //加入缓存区中的定时任务
                for (int tmpIndex = 0; tmpIndex < tmpFrameLst.Count; tmpIndex++) {
                    taskFrameLst.Add(tmpFrameLst[tmpIndex]);
                }
                tmpFrameLst.Clear();
            }
        }

        frameCounter += 1;
        //遍历检测任务是否达到条件
        for (int index = 0; index < taskFrameLst.Count; index++) {
            PEFrameTask task = taskFrameLst[index];
            if (frameCounter < task.destFrame) {
                continue;
            }
            else {
                Action cb = task.callback;
                try {
                    if (taskHandle != null) {
                        taskHandle(cb, task.tid);
                    }
                    else {
                        if (cb != null) {
                            cb(task.tid);
                        }
                    }
                }
                catch (Exception e) {
                    LogInfo(e.ToString());
                }

                //移除已经完成的任务
                if (task.count == 1) {
                    taskFrameLst.RemoveAt(index);
                    index--;
                    recTidLst.Add(task.tid);
                }
                else {
                    if (task.count != 0) {
                        task.count -= 1;
                    }
                    task.destFrame += task.delay;
                }
            }
        }
    }

    #region TimeTask
    public int AddTimeTask(Action callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
        if (timeUnit != PETimeUnit.Millisecond) {
            switch (timeUnit) {
                case PETimeUnit.Second:
                    delay = delay * 1000;
                    break;
                case PETimeUnit.Minute:
                    delay = delay * 1000 * 60;
                    break;
                case PETimeUnit.Hour:
                    delay = delay * 1000 * 60 * 60;
                    break;
                case PETimeUnit.Day:
                    delay = delay * 1000 * 60 * 60 * 24;
                    break;
                default:
                    LogInfo("Add Task TimeUnit Type Error...");
                    break;
            }
        }
        int tid = GetTid(); ;
        nowTime = GetUTCMilliseconds();
        lock (lockTime) {
            tmpTimeLst.Add(new PETimeTask(tid, callback, nowTime + delay, delay, count));
        }
        return tid;
    }
    public void DeleteTimeTask(int tid) {
        lock (lockTime) {
            tmpDelTimeLst.Add(tid);
            //LogInfo("TmpDel ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        }
        /*
         bool exist = false;

         for (int i = 0; i < taskTimeLst.Count; i++) {
             PETimeTask task = taskTimeLst[i];
             if (task.tid == tid) {
                 //taskTimeLst.RemoveAt(i);
                 for (int j = 0; j < tidLst.Count; j++) {
                     if (tidLst[j] == tid) {
                         //tidLst.RemoveAt(j);
                         break;
                     }
                 }
                 exist = true;
                 break;
             }
         }

         if (!exist) {
             for (int i = 0; i < tmpTimeLst.Count; i++) {
                 PETimeTask task = tmpTimeLst[i];
                 if (task.tid == tid) {
                     //tmpTimeLst.RemoveAt(i);
                     for (int j = 0; j < tidLst.Count; j++) {
                         if (tidLst[j] == tid) {
                             //tidLst.RemoveAt(j);
                             break;
                         }
                     }
                     exist = true;
                     break;
                 }
             }
         }

         return exist;
         */
    }
    public bool ReplaceTimeTask(int tid, Action callback, float delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
        if (timeUnit != PETimeUnit.Millisecond) {
            switch (timeUnit) {
                case PETimeUnit.Second:
                    delay = delay * 1000;
                    break;
                case PETimeUnit.Minute:
                    delay = delay * 1000 * 60;
                    break;
                case PETimeUnit.Hour:
                    delay = delay * 1000 * 60 * 60;
                    break;
                case PETimeUnit.Day:
                    delay = delay * 1000 * 60 * 60 * 24;
                    break;
                default:
                    LogInfo("Replace Task TimeUnit Type Error...");
                    break;
            }
        }
        nowTime = GetUTCMilliseconds();
        PETimeTask newTask = new PETimeTask(tid, callback, nowTime + delay, delay, count);

        bool isRep = false;
        for (int i = 0; i < taskTimeLst.Count; i++) {
            if (taskTimeLst[i].tid == tid) {
                taskTimeLst[i] = newTask;
                isRep = true;
                break;
            }
        }

        if (!isRep) {
            for (int i = 0; i < tmpTimeLst.Count; i++) {
                if (tmpTimeLst[i].tid == tid) {
                    tmpTimeLst[i] = newTask;
                    isRep = true;
                    break;
                }
            }
        }

        return isRep;
    }
    #endregion

    #region FrameTask
    public int AddFrameTask(Action callback, int delay, int count = 1) {
        int tid = GetTid();
        lock (lockTime) {
            tmpFrameLst.Add(new PEFrameTask(tid, callback, frameCounter + delay, delay, count));
        }
        return tid;
    }
    public void DeleteFrameTask(int tid) {
        lock (lockFrame) {
            tmpDelFrameLst.Add(tid);
        }
        /*
        bool exist = false;

        for (int i = 0; i < taskFrameLst.Count; i++) {
            PEFrameTask task = taskFrameLst[i];
            if (task.tid == tid) {
                //taskFrameLst.RemoveAt(i);
                for (int j = 0; j < tidLst.Count; j++) {
                    if (tidLst[j] == tid) {
                        //tidLst.RemoveAt(j);
                        break;
                    }
                }
                exist = true;
                break;
            }
        }

        if (!exist) {
            for (int i = 0; i < tmpFrameLst.Count; i++) {
                PEFrameTask task = tmpFrameLst[i];
                if (task.tid == tid) {
                    //tmpFrameLst.RemoveAt(i);
                    for (int j = 0; j < tidLst.Count; j++) {
                        if (tidLst[j] == tid) {
                            //tidLst.RemoveAt(j);
                            break;
                        }
                    }
                    exist = true;
                    break;
                }
            }
        }

        return exist;
        */
    }
    public bool ReplaceFrameTask(int tid, Action callback, int delay, int count = 1) {
        PEFrameTask newTask = new PEFrameTask(tid, callback, frameCounter + delay, delay, count);

        bool isRep = false;
        for (int i = 0; i < taskFrameLst.Count; i++) {
            if (taskFrameLst[i].tid == tid) {
                taskFrameLst[i] = newTask;
                isRep = true;
                break;
            }
        }

        if (!isRep) {
            for (int i = 0; i < tmpFrameLst.Count; i++) {
                if (tmpFrameLst[i].tid == tid) {
                    tmpFrameLst[i] = newTask;
                    isRep = true;
                    break;
                }
            }
        }

        return isRep;
    }
    #endregion

    public void SetLog(Action log) {
        taskLog = log;
    }
    
    public void SetHandle(Action, int> handle) {
        taskHandle = handle;
    }

    public void Reset() {
        tid = 0;
        tidLst.Clear();
        recTidLst.Clear();

        tmpTimeLst.Clear();
        taskTimeLst.Clear();

        tmpFrameLst.Clear();
        taskFrameLst.Clear();

        taskLog = null;
        srvTimer.Stop();
    }

    public int GetYear() {
        return GetLocalDateTime().Year;
    }
    public int GetMonth() {
        return GetLocalDateTime().Month;
    }
    public int GetDay() {
        return GetLocalDateTime().Day;
    }
    public int GetWeek() {
        return (int)GetLocalDateTime().DayOfWeek;
    }
    public DateTime GetLocalDateTime() {
        DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(nowTime));
        return dt;
    }
    public double GetMillisecondsTime() {
        return nowTime;
    }
    public string GetLocalTimeStr() {
        DateTime dt = GetLocalDateTime();
        string str = GetTimeStr(dt.Hour) + ":" + GetTimeStr(dt.Minute) + ":" + GetTimeStr(dt.Second);
        return str;
    }

    #region Tool Methonds
    private int GetTid() {
        lock (lockTid) {
            tid += 1;

            //安全代码,以防万一
            while (true) {
                if (tid == int.MaxValue) {
                    tid = 0;
                }

                bool used = false;
                for (int i = 0; i < tidLst.Count; i++) {
                    if (tid == tidLst[i]) {
                        used = true;
                        break;
                    }
                }
                if (!used) {
                    tidLst.Add(tid);
                    break;
                }
                else {
                    tid += 1;
                }
            }
        }

        return tid;
    }
    private void RecycleTid() {
        for (int i = 0; i < recTidLst.Count; i++) {
            int tid = recTidLst[i];

            for (int j = 0; j < tidLst.Count; j++) {
                if (tidLst[j] == tid) {
                    tidLst.RemoveAt(j);
                    break;
                }
            }
        }
        recTidLst.Clear();
    }
    private void LogInfo(string info) {
        if (taskLog != null) {
            taskLog(info);
        }
    }
    private double GetUTCMilliseconds() {
        TimeSpan ts = DateTime.UtcNow - startDateTime;
        return ts.TotalMilliseconds;
    }
    private string GetTimeStr(int time) {
        if (time < 10) {
            return "0" + time;
        }
        else {
            return time.ToString();
        }
    }
    #endregion

    class PETimeTask {
        public int tid;
        public Action callback;
        public double destTime;//单位:毫秒
        public double delay;
        public int count;

        public PETimeTask(int tid, Action callback, double destTime, double delay, int count) {
            this.tid = tid;
            this.callback = callback;
            this.destTime = destTime;
            this.delay = delay;
            this.count = count;
        }
    }

    class PEFrameTask {
        public int tid;
        public Action callback;
        public int destFrame;
        public int delay;
        public int count;

        public PEFrameTask(int tid, Action callback, int destFrame, int delay, int count) {
            this.tid = tid;
            this.callback = callback;
            this.destFrame = destFrame;
            this.delay = delay;
            this.count = count;
        }
    }
}

public enum PETimeUnit {
    Millisecond,
    Second,
    Minute,
    Hour,
    Day
}

使用示例代码

PETimer控制台工程案例代码:

using System;
using System.Threading;
using System.Collections.Generic;

namespace ConsoleProjects {
    class Program {
        private static readonly string obj = "lock";

        static void Main(string[] args) {
            Console.WriteLine("Test Start!");
            //Test1();
            Test2();
        }

        //第一种用法:运行线程检测并处理任务
        static void Test1() {
            //运行线程驱动计时
            PETimer pt = new PETimer();
            pt.SetLog((string info) => {
                Console.WriteLine("LogInfo:" + info);
            });

            pt.AddTimeTask((int tid) => {
                Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
            }, 10, PETimeUnit.Millisecond, 0);

            while (true) {
                pt.Update();
            }
        }

        //第二种用法:独立线程检测并处理任务
        static void Test2() {
            Queue tpQue = new Queue();
            //独立线程驱动计时
            PETimer pt = new PETimer(5);
            pt.SetLog((string info) => {
                Console.WriteLine("LogInfo:" + info);
            });


            int id = pt.AddTimeTask((int tid) => {
                Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
            }, 3000, PETimeUnit.Millisecond, 0);

            //设置回调处理器
            /*
            pt.SetHandle((Action cb, int tid) => {
                if (cb != null) {
                    lock (obj) {
                        tpQue.Enqueue(new TaskPack(tid, cb));
                    }
                }
            });
            */
            while (true) {
                string ipt = Console.ReadLine();
                if (ipt == "a") {
                    pt.DeleteTimeTask(id);
                }


                if (tpQue.Count > 0) {
                    TaskPack tp = null;
                    lock (obj) {
                        tp = tpQue.Dequeue();
                    }
                    tp.cb(tp.tid);
                }
            }
        }
    }

    //任务数据包
    class TaskPack {
        public int tid;
        public Action cb;
        public TaskPack(int tid, Action cb) {
            this.tid = tid;
            this.cb = cb;
        }
    }
}

PETimer集成到Unity案例代码:

using UnityEngine;

public class GameStart : MonoBehaviour {
    PETimer pt = new PETimer();

    int tempID = -1;
    private void Start() {
        //时间定时
        pt.AddTimeTask(TimerTask, 500, PETimeUnit.Millisecond, 3);
        //帧数定时
        pt.AddFrameTask(FrameTask, 100, 3);

        //定时替换/删除
        tempID = pt.AddTimeTask((int tid) => {
            Debug.Log("定时等待替换......");
        }, 1, PETimeUnit.Second, 0);
    }

    private void Update() {
        pt.Update();

        //定时替换
        if (Input.GetKeyDown(KeyCode.R)) {

            bool succ = pt.ReplaceTimeTask(tempID, (int tid) => {
                Debug.Log("定时等待删除......");
            }, 2, PETimeUnit.Second, 0);

            if (succ) {
                Debug.Log("替换成功");
            }
        }

        //定时删除
        if (Input.GetKeyDown(KeyCode.D)) {
            pt.DeleteTimeTask(tempID);
        }
    }

    void TimerTask(int tid) {
        Debug.Log("TimeTask:" + System.DateTime.UtcNow);
    }

    void FrameTask(int tid) {
        Debug.Log("FrameTask:" + System.DateTime.UtcNow);
    }
}

 

你可能感兴趣的:(个人学习,Unity,C#)