Unity 定时回调系统技术专题(Siki Plane)
官网Plane的GitHub
官网视频
官方B站视频
在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。
101 物体的生命周期
//有无勾勾
FixedUpdate
private void FixedUpdate(){public float timer = 0f;public float time = 1f;timer += Time.fixedDeltaTime;
//fixedDeltaTime。下图是1秒1次输出
(问题) NullReferenceException: Object reference not set to an instance of an object
//一直存在,但不影响运行
//unityHub下载不了2020(下载到快完了,全部没了)
//所以从官网下安装包,但又找不到中文包,所以从unityHub下载的2018中文包复制过来
//不知道是不是这原因
102 FixedUpdate
不是固定步长递增
private void FixedUpdate(){Test02();}void Test02(){print("启动时长:" + Time.realtimeSinceStartup);print("启动时长:" + Time.realtimeSinceStartupAsDouble);print("启动帧数:" + Time.renderedFrameCount);}
最大允许时间步进
驱动刚体运动时,超过最大允许时间步进,就终止此次运算,进行主循环运行,以此保证时间。
所以实际刚体运动会慢点,但忽略不计。
103 脚本执行顺序
需求
//一个物体下两个脚本的执行顺序
//一个物体下同一个脚本的执行顺序
不同脚本的执行顺序
//AB对比同一个物体下运行后,组件的顺序就固定了,后面再调整无用
//AC对比,同一物体下,组建运行顺序从下到上(不同物体也是从下到上)
Unity自带的脚本执行顺序(针对不同脚本)
不同物体下同一个脚本执行顺序
//第一次从下到上
//后面再调整物体顺序,不便了
//作者推荐的,用一个父节点来管理
public Transform aTrans;public Transform bTrans;// Start is called before the first frame updatevoid Start(){bTrans.GetComponent<NewBehaviourScript103_D_1>().D1();aTrans.GetComponent<NewBehaviourScript103_D_1>().D1();}
(问题) 学习时脚本分类
到网页复制标题
到VS利用Alt键盘修改标题(一般不允许加空格,如下下图是不行的)
MD、bat(另存为ASNI编码,默认的UTF8是会乱码)
“灵者更名”添加空格
//MD 新建文件夹
104 理解Unity主线程设计思想1
//一个线程,主线程,串行运行
//逻辑帧,一个物体从Update到下一次Update的时间
105 理解Unity主线程设计思想2(线程ID)
//不允许在主线程之外访问transform,限制编程环境单线程
//底层运用线程池,不需要开发者管理
void Start(){ThreadStart threadStart = new ThreadStart(ThreadNew);Thread thread = new Thread(threadStart);thread.Start();print("主线程" + Thread.CurrentThread.ManagedThreadId);}void ThreadNew(){print("新线程" + Thread.CurrentThread.ManagedThreadId);}
106 协程的常规使用1(开启协程的两种方式的区别)
//第一种调用参数上限没有限制
//第一种调用参数上限为1
void Start(){StartCoroutine(A(1,2));//StartCoroutine("A",1);}IEnumerator A(int a,int b){print("前");yield return new WaitForSeconds(2f);print("后");}
107 协程的常规使用2(终止协程)
问答
//我也测试到StopCoroutine(A()); 对 StartCoroutine(A()); 无效
//视频也讲到StopCoroutine(A()); 对 StartCoroutine(“A”); 无效
[Tooltip("协程")] Coroutine coroutine;[Tooltip("迭代器")] IEnumerator enumerator;// Start is called before the first frame updatevoid Start(){}IEnumerator A(){ print("前");yield return new WaitForSeconds(2f);print("后"); }// Update is called once per framevoid Update(){StartA();StopA();}void StartA(){if (Input.GetKeyDown(KeyCode.Alpha1)){StartCoroutine(A()); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha2)){StartCoroutine("A"); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha3)){coroutine = StartCoroutine(A()); print("开启");}else if (Input.GetKeyDown(KeyCode.Alpha4)){enumerator = A(); print("开启");StartCoroutine(enumerator);}}void StopA(){if (Input.GetKeyUp(KeyCode.Alpha1)){StopCoroutine(A()); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha2)){StopCoroutine("A"); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha3)){StopCoroutine(coroutine); print("结束");}else if (Input.GetKeyUp(KeyCode.Alpha4)){StopCoroutine(enumerator); print("结束");}else if (Input.GetKeyDown(KeyCode.Alpha5)){StopAllCoroutines(); print("结束");}}
108 深入理解协程原理1
事件函数的执行顺序
//协程的最大作用是加载资源
失效开启协程的物体
//开启协程后,失效物体,再次激活物体,协程不运行(Unity那张生命周期图,OnDisabled就没协程了)
协程串协程
//单线程的体现
void Start(){StartCoroutine(A());}IEnumerator A(){print("1");yield return new WaitForSeconds(3f);print("2");yield return StartCoroutine(B());print("3");}IEnumerator B(){print("4");yield return new WaitForSeconds(2f);print("5");}
协程串协程串协程
//我也蒙这翻译,再串一段协程
//yield像一堵墙,执行顺序如下。方框处是整个协程彻底结束的时候
void Start(){StartCoroutine(A());}IEnumerator A(){print("1");yield return new WaitForSeconds(3f);print("2");yield return StartCoroutine(B());print("3");}IEnumerator B(){print("4");yield return new WaitForSeconds(2f);print("5");yield return StartCoroutine(C());print("6");}IEnumerator C(){print("7");yield return new WaitForSeconds(2f);print("8");}
109 深入理解协程原理2(资源加载)
//异步加载Resources文件夹里的某一物体
ResourceRequest resourceRequest;GameObject go;// Start is called before the first frame updatevoid Start(){StartCoroutine(LoadResourcesAsync());}IEnumerator LoadResourcesAsync(){resourceRequest = Resources.LoadAsync<GameObject>("Cube");//类型,名字yield return resourceRequest;go = ( resourceRequest.asset) as GameObject;if (go != null){Instantiate(go);}else{throw new System.Exception("异常");}} // Update is called once per framevoid Update(){if (resourceRequest != null && go != null){print(resourceRequest.progress);}}
110 实现思路分析
//服务器定时任务多
//协程依赖MonoBehavior依赖于Unity,不能在服务器跑Unity。如下图
//借鉴携程是帧驱动的思想实现计时器
201 搭建测试环境
脚本TimerSys(单例),GameRoot
202 初始化脚本顺序
以往单例放在Awake
public class TimerSys : MonoBehaviour
{public static TimerSys _instance;void Awake(){_instance = this;}public void AddTimeTask(){print("定时任务");}
public class GameRoot : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){TimerSys._instance.AddTimeTask();}
现在封装成方法
//加一个按钮事件
//单例采用封装方法,一次控制顺序,防止单例后运行
public class GameRoot : MonoBehaviour
{TimerSys timerSys;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}public void OnAddtimeTaskClick(){timerSys.AddTimeTask();}
public class TimerSys : MonoBehaviour
{public static TimerSys _instance;public void Init(){_instance = this;}public void AddTimeTask(){print("定时任务");}
203 基础定时功能实现
//之前“黑暗之光”时,我用委托做了定时器(用Time.deltaTime的)。有点类似
//方法复制那里如果不是需要加上系统运行时间,参数改为PETimeTask也不错
(问题) NullReferenceException: Object reference not set to an instance of an object
NullReferenceException: Object reference not set to an instance of an object
//空指针
//没有做初始化taskList = new List();
public class TimerSys : MonoBehaviour
{public List<PETimetask> taskList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();}
PETimetask
//任务数据类
using System;public class PETimetask
{public Action callback;//要定时的任务public float destTime;//延时几秒
}
GameRoot
public class GameRoot : MonoBehaviour
{TimerSys timerSys;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}public void OnAddtimeTaskClick(){timerSys.AddTimeTask(FuncA, 2f);}void FuncA(){print("FuncA");}
}
TimerSys
public class TimerSys : MonoBehaviour
{public List<PETimetask> taskList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();}public void AddTimeTask(Action callback, float destTime){print("添加定时任务");PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup + destTime;task.callback = callback;task.destTime = time;//taskList.Add(task);}// Update is called once per framevoid Update(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup < task.destTime){continue;}else{if (task.callback != null)//这个判空的直觉我体会不到{task.callback();} taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引}}}
}
效果
204 增加临时缓存列表
需求
//多线程定时
//增加缓存列表taskTmpList,避免加锁提高效率
代码
public class TimerSys : MonoBehaviour
{[Tooltip("定时任务列表")] public List<PETimetask> taskList;[Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();taskTmpList = new List<PETimetask>(); }#region 添加定时任务public void AddTimeTask(Action callback, float delay)//默认毫秒{PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup+ delay;task.callback = callback;task.destTime = time;//taskTmpList.Add(task);}#endregion///// <summary>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){ for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<para />/// </summary> void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup < task.destTime){continue;}else{if (task.callback != null)//这个判空的直觉我体会不到{task.callback();}taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }}}void Update(){LoadTaskTmpList();RunTaskList(); }}
(需求) 注释方法,全局提示
C# 方法注释,让参数、返回结果可见,并且实现换行显示
//其实这时我需求只需要提示方法是干什么的就行了
205 增加时间单位设置功能
//Time.realtimeSinceStartu的单位是秒,所以毫秒*1000f
//GameRoot的调用相应调整
public class TimerSys : MonoBehaviour
{[Tooltip("定时任务列表")] public List<PETimetask> taskList;[Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;public static TimerSys _instance;public void Init(){_instance = this;taskList = new List<PETimetask>();taskTmpList = new List<PETimetask>(); }#region 添加定时任务public void AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond)//默认毫秒{delay = UnitConversion(delay, unit);//PETimetask task = new PETimetask();float time = Time.realtimeSinceStartup * 1000f+ delay;task.callback = callback;task.destTime = time;//taskTmpList.Add(task);}/// <summary>/// 单位换算成毫秒<para />/// <param name="delay">数值<para /></param>/// <param name="unit">delay的时间单位<para /></param>/// <returns>返回true换算为毫秒的delay</returns>/// </summary> float UnitConversion(float delay, PETimeUnit unit = PETimeUnit.MillSecond)//{switch (unit){case PETimeUnit.MillSecond: break;case PETimeUnit.Second: delay = delay * 1000f; break;case PETimeUnit.Minute: delay = delay * 1000f * 60f; break;case PETimeUnit.Hour: delay = delay * 1000f * 60f * 60f; break;case PETimeUnit.Day: delay = delay * 1000f * 60f * 60f * 24f; break;default: { throw new Exception("异常"); }}return delay;}#endregion///// <summary>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){ for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<para />/// </summary> void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup * 1000f < task.destTime){continue;}else{if (task.callback != null)//这个判空的直觉我体会不到{task.callback();}taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }}}void Update(){LoadTaskTmpList();RunTaskList(); }
}
206 增加任务循环功能
需求
//delay,执行完一次后,destTime+=delay
//count>1,执行后-1
//count0循环执行
//count1,执行后可以移除该定时任务
//
//采用构造方法
//GameRoot传参调用3次
(代码) PETimetask
public class PETimetask
{public Action callback;//要定时的任务public float destTime;//延时到游戏时间结束public int count;//执行次数public float delay;//延时几秒public PETimetask(Action callback, float destTime, int count, float delay){this.callback = callback;this.destTime = destTime;this.count = count;this.delay = delay;}
}
(代码) TimerSys
void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup * 1000f < task.destTime){continue;}else{if (task.callback != null)//我没有意识咋{task.callback();}if (task.count == 1){taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }else{if (task.count != 0){task.count--; }//定义0==循环task.destTime += task.delay;}......public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默认毫秒{delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f+ delay;PETimetask task = new PETimetask(callback, time, count, delay);//taskTmpList.Add(task);}
(问题) 可选参数必须出现在所有必要参数之后
//将int count提到前面
//视频是int count=1,也是一个可选参数,所以不用提
207 生成定时任务全局ID
需求分析
//锁里面处理id
//处理超出id(int类型)范围
//int.MaxValue
//锁
//我用taskList[i].id来给id遍历。视频是新建了一个idList专门存储id。(我想尽量减少变量,可能以后有问题,现在找不到问题)
//移除或减少次数,对于idList,都要移除id
(代码) TimerSys
//调用是执行3次
[Tooltip("定义锁")] private static readonly string obj="lock";[Tooltip("全局id,初始值经过方法后是0,所以-1")] public int id;//[Tooltip("id列表")] public List<int> idList;public static TimerSys _instance;public void Init(){......//idList = new List<int>();id = -1;}public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默认毫秒{......int id = GenerateId();PETimetask task = new PETimetask(callback, time, count, delay, id);//taskTmpList.Add(task);//idList.Add(id);}/// <summary>/// 生成唯一索引id<para />/// </summary> int GenerateId(){ lock(obj)//多线程显示唯一id就要锁{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用过了, bool isUsed = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isUsed = true;break;}}if (isUsed) id++;else break;} }return id;}
208 增加任务删除功能
//根据id,遍历taskList和taskTmpList,悠久移除,返回true
/;/没采用idList,只用taskList代码简洁了一些
(代码) TimeSys
/// <summary>/// 删除定时任务<para />/// </summary>public bool DeleteTimeTask(int id){bool isExisted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isExisted = true;taskList.RemoveAt(i);break;}}for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isExisted = true;taskTmpList.RemoveAt(i);break;}}return isExisted;}
//调用是3次,下图在第二次进行删除定时任务
209 增加任务替换功能
(需求)
//遍历两个列表进行替换
//原方法是循环输出FuncA,新方法定为输出一次FuncB
(代码)
public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f + delay;PETimetask task = new PETimetask(callback, time, count, delay, id);////必在两个表之一bool isReplaced = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isReplaced = true;taskList[i] = task;break;}}if (isReplaced == false){for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isReplaced = true;taskTmpList[i] = task;break;}}}return isReplaced;}
(问题) FuncB执行了两次
taskTmpList.Add(task);使其多运行了一次
public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){delay = UnitConversion(delay, unit);// float time = Time.realtimeSinceStartup * 1000f + delay;PETimetask task = new PETimetask(callback, time, count, delay, id);//// taskTmpList.Add(task);
210 清理定时任务全局ID
(需求)
定义一个列表,存储移除的task的id
该列表不为空的,和idList做对比,有的话就移除掉idList里的id
由于我是指直接取taskList[i].id这种形式,没有涉及idList,所以不需要
定义帧的任务数据类和Sys
我是新建一个类FrameTimerSys,原来的命名为TimeTimerSys。视频将这两部分放一起
211 帧定时任务开发1
需求
//使用lambda表达式来简化调用的输出函数
//System.DateTime.Now,系统现在时间
//回调加捕捉异常
lambda
public void OnAddTimeTaskClick(){id=timerSys.AddTimeTask( ()=> {print("FuncA,id:" + id);print(",时间:"+System.DateTime.Now); },1000f, PETimeUnit.MillSecond,0);//0是循环}
回调加捕捉异常
void RunTaskList(){if (taskList.Count <= 0) return;for (int i = 0; i < taskList.Count; i++){PETimetask task = taskList[i];if (Time.realtimeSinceStartup * 1000f < task.destTime){continue;}else{try{if (task.callback != null)//我没有意识要检查非空{task.callback();}}catch (Exception e){print(e.ToString());}......
212 帧定时任务开发2;213 测试帧定时任务
需求
//视频用一个每帧递增的frameCounter来代替Time.renderedFrameCount。
//我将frameCounter在没有定时任务时置于0
//新建帧任务数据类
//视频将帧定时的方法跟时间定时,写在一个类,我拆了出来,虽然双方的一些Tool类型的方法一样
PEFrameTask
//帧任务数据类
public class PEFrameTask
{public Action callback;//要定时的任务public int destFrame;//有定时任务时,frameCounter+delaypublic int count;//执行次数public int delay;//延时几帧public int id;//索引public PEFrameTask(Action callback, int destFrame, int count, int delay, int id){this.id = id;this.callback = callback;this.destFrame = destFrame;this.count = count;this.delay = delay;}
}
FrameTimerSys
public class FrameTimerSys : MonoBehaviour
{[Tooltip("定时任务列表")] public List<PEFrameTask> taskList;[Tooltip("缓存的定时任务列表")] public List<PEFrameTask> taskTmpList;[Tooltip("定义锁")] private static readonly string obj="lock";[Tooltip("全局id,初始值经过方法后是0,所以-1")] public int id;[Tooltip("有定时任务时的纪元帧")] public int frameCounter = 0;//[Tooltip("id列表")] public List<int> idList;public static FrameTimerSys _instance;public void Init(){_instance = this;taskList = new List<PEFrameTask>();taskTmpList = new List<PEFrameTask>();//idList = new List<int>();id = -1;}#region 增删改public int AddTimerTask(Action callback,int delay, int count=1)//默认毫秒{// int id = GenerateId();PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay,id);//taskTmpList.Add(task);return id;}/// <summary>/// 删除定时任务<para />/// </summary>public bool DeleteTimerTask(int id){bool isExisted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isExisted = true;taskList.RemoveAt(i);break;}}for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isExisted = true;taskTmpList.RemoveAt(i);break;}}return isExisted;}/// <summary>/// 替换定时任务<para />/// </summary>public bool ReplaceTimerTask(int id,Action callback, int delay, int count = 1){// PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay, id);////必在两个表之一bool isReplaced = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isReplaced = true;taskList[i] = task;break;}}if (isReplaced == false){for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){isReplaced = true;taskTmpList[i] = task;break;}}}return isReplaced;}#endregion///// <summary>/// 加载缓存的临时列表<para />/// </summary>void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印输出,所以returnfor (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}/// <summary>/// 执行定时任务<para />/// </summary> void RunTaskList(){if (taskList.Count <= 0){frameCounter=0;return;}frameCounter++;for (int i = 0; i < taskList.Count; i++){PEFrameTask task = taskList[i];if (frameCounter < task.destFrame){continue;}else{try{if (task.callback != null)//我没有意识要检查非空{task.callback();}}catch (Exception e){print(e.ToString());}if (task.count == 1){taskList.Remove(task);i--;//移除List自动接上去,所以还需要从原索引 }else{if (task.count != 0){task.count--;//idList.Remove(task.id);}else{//定义0==循环}task.destFrame += task.delay;}}}}void Update(){LoadTaskTmpList();RunTaskList(); }#region Tool/// <summary>/// 生成唯一索引id<para />/// </summary> int GenerateId(){lock (obj)//多线程显示唯一id就要锁{id++;while (true){//超出int最大值if (id == int.MaxValue){id = 0;}//是否用过了, bool isUsed = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isUsed = true;break;}}if (isUsed) id++;else break;}}return id;}#endregion
GameRoot_Frame 调用测试
public class GameRoot_Frame : MonoBehaviour
{FrameTimerSys timerSys;[Tooltip("为了测试删除,替换")] public int id;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<FrameTimerSys>();timerSys.Init();}public void OnAddTimerTaskClick(){id=timerSys.AddTimerTask( ()=> {print("FuncA,id:" +id + " " + "帧数:"+Time.renderedFrameCount); },60, 0);//0是循环}public void OnDeleteTimerTaskClick(){timerSys.DeleteTimerTask(id);}public void OnReplaceTimerTaskClick(){bool isReplaced=timerSys.ReplaceTimerTask(id,()=> { print("FuncB"); }, 60, 1);if (isReplaced) { print("替换成功!"); }}}
效果
301 剥离Monobehaviour依赖
需求
//从这开始是服务器上的定时器,官网学员反应难度跟前面两部分对比明显(我也感受到了,没有一个视频敲一些出一个效果的步步前进)
//服务器不能装Unity,所以不能用using UnityEngine。去掉using UnityEngine;后Debug,print,Time,Update等都不能用了。所以要解决这几点。
//GameRoot,UI按钮调用
//TimerSys看,实例并且引用PETime的方法
//PETimer,去除using UnityEngine;和MonoBehaviour,放在服务器上。移植了前两个TimeSys的主体功能。重写update,打印,时间等原来依赖于Unity的部分
增加 删除 更新
GameRoot
public class GameRoot : MonoBehaviour
{TimerSys timerSys;[Tooltip("为了测试删除")] public int id;// Start is called before the first frame updatevoid Start(){timerSys = GetComponent<TimerSys>();timerSys.Init();}#region 时间public void OnAddTimeTaskClick(){id = timerSys.AddTimeTask(() =>{print("FuncA,id:" + id);print(",时间:" + System.DateTime.Now);},1000f, PETimeUnit.MillSecond, 0);//0是循环}public void OnDeleteTimeTaskClick(){timerSys.DeleteTimeTask(id);}public void OnReplaceTimeTaskClick(){bool isReplaced = timerSys.ReplaceTimeTask(id, () => print("FuncB"), 1000f, PETimeUnit.MillSecond, 1);if (isReplaced) { print("替换成功!"); }}#endregion#region 帧public void OnAddFrameTaskClick(){id = timerSys.AddFrameTask(() =>{print("FuncA,id:" + id);print(",时间:" + System.DateTime.Now);},60, 0);//0是循环}public void OnDeleteFrameTaskClick(){timerSys.DeleteFrameTask(id);}public void OnReplaceFrameTaskClick(){bool isReplaced = timerSys.ReplaceFrameTask(id, () => { print("FuncB"); }, 60, 1);if (isReplaced) { print("帧替换成功!"); }}#endregion
}
TimerSys
public class TimerSys :MonoBehaviour
{public static TimerSys _instance;public PETimer pt;public void Init(){_instance = this;pt=new PETimer();pt.Init();pt.SetLog((string log)=> { print("初始化"+log); });}#region 定时任务 增 删 改public int AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{return pt.AddTimeTask(callback, delay, unit, count);}public bool DeleteTimeTask(int id){return pt.DeleteTimeTask(id);}public bool ReplaceTimeTask(int id, Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1){return pt.ReplaceTimeTask(id,callback, delay, unit, count);}#endregion#region 帧 增 删 改public int AddFrameTask(Action callback, int delay, int count = 1)//默认毫秒{return pt.AddFrameTask(callback,delay,count);}public bool DeleteFrameTask(int id){return pt.DeleteFrameTask(id);}public bool ReplaceFrameTask(int id, Action callback, int delay, int count = 1){return pt.ReplaceFrameTask(id, callback, delay, count);}#endregionprivate void Update(){pt.Update();}
}
302 设置日志处理
需求
//实现打印,update(没有继承MonoBehavior,需要TimerSys来驱动), 构造
PETimer的打印
//SetLog被外界调用,传入一个方法引用给自己的委托log
//Log规定自己的委托将会执行带字符串参数的方法体
// PETimer
private Action<string> log;public void SetLog(Action<String> log){if (log != null){this.log = log;} }private void Log(string log){this.log(log);}
//TimerSys
public void Init(){......pt=new PETimer();pt.Init();pt.SetLog((string log)=> { print("初始化"+log); });}
PETimer的Update(靠其他脚本的Update来驱动)
// PETimer
public void Update(){LoadTaskTmpList();RunTaskList();Frame_LoadTaskTmpList();Frame_RunTaskList();}
//TimerSys
private void Update(){pt.Update();}
PETimer的构造
//我加上这一段报空
public PETimer(){taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();}
303 计算机纪年与UTC时区
//当年出现C语言版本的Unix,32位的int按秒计算是68.1年。两个因素所以选1970public DateTime startDateTime = new DateTime ( 1970, 1, 1, 0, 0, 0,0 );public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}
304 剥离UnityEngine的时间计算依赖
//将destTime和delay的类型改为double
//PETimer中将原本的 Time.realtimeSinceStartup改为GetUTCMillSeconds()
//视频定义了一个全局的nowTime=GetUTCMillSeconds();
PETimer的代码结构
具体的看Plane的github
官网Plane的GitHub PlaneZhong/PETimer
305 移植到控制台工程环境
需求
//VS 新建项目【控制台】添加到原方案
//拖过来PETimer,PETimeTask
Program.cs
using System;namespace TimedCallback_VS
{class Program{static void Main(string[] args){Test();Console.WriteLine("Hello World!");Console.ReadKey();}static void Test(){PETimer pt = new PETimer();pt.Init();pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask( ()=> { Console.WriteLine(pt.id+" "+DateTime.Now); }, 1000d,PETimeUnit.MillSecond,0);while (true){pt.Update();}}}
}
306 Timer线程计时器
Thread.CurrentThread.ManagedThreadId线程Id
static void ThreadTest()//那个线程空闲就用哪个线程{double millSeconds = 50d;System.Timers.Timer t = new System.Timers.Timer(millSeconds);//单独Timers有二义性t.AutoReset = true;t.Elapsed += (Object sendeer, ElapsedEventArgs args) =>{Console.WriteLine("线程ID:{0}", Thread.CurrentThread.ManagedThreadId);};t.Start();}
307 整合Timer线程计器
program
static void DetachedThread()//独立线程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask(() => { Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);}
PETimer
public PETimer(int interval=0)//{Init();//发现报空错误,是未初始化//taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();//if (interval >= 0){DetachedThread(interval);}}void DetachedThread(int interval){System.Timers.Timer t = new System.Timers.Timer(interval);//执行Update时间间隔t.AutoReset = true;t.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};t.Start();}
效果
308 增加任务回调tid参数
需求
将任务数据类(时间和帧)的callback类型改为public Action callback;。并对相关引用(根据报错)进行修改
PETimer
public System.Timers.Timer serverTimer;public PETimer(int interval=0)//interval默认时间间隔{Init();//发现报空错误,是未初始化//taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();//if (interval >= 0){DetachedThread(interval);}}void DetachedThread(int interval){serverTimer = new System.Timers.Timer(interval);//执行Update时间间隔serverTimer.AutoReset = true;serverTimer.Elapsed += (Object sender, ElapsedEventArgs args) =>{Update();};serverTimer.Start();}void Reset()//重启服务器{id = 0;taskList.Clear();taskTmpList.Clear();frame_taskList.Clear();frame_taskTmpList.Clear();log = null;serverTimer.Stop();}
Program
static void DetachedThread()//独立线程{PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) => { Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); },100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0
309 增加任务Handle设置功能
需求
307的的线程Id是变化的,
1、主体在主线程执行,一些文件I/O分流到独立线程,然后再回到主线程
2、逻辑部分也是多线程,数据修改部分加锁(性能高,开发麻烦,死锁)
如果要采用第一种方式。写任务柄
在Program定义任务包,实现任务柄的入队出队(加锁)
PETimer
修改两次(时间和帧,以下只显示时间的)运行
public Action<Action<int> ,int > taskHandle;//回调,idpublic void SetHandle(Action<Action<int>, int> handle){this.taskHandle = handle;}void RunTaskList(){......else{Action<int> callback = task.callback;try{if (taskHandle != null){taskHandle(callback, task.id);}else if (task.callback != null)//我没有意识要检查非空{task.callback(task.id);}}......
Program
private static readonly string obj="lock";static void DetachedThreadBackMainThread()//独立线程{Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });pt.AddTimeTask((int id) =>{Console.WriteLine("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},100d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);//对立面存在很多等待执行的任务pt.SetHandle( (Action<int> callback, int id)=>{if (callback != null){lock (obj){taskPackQuene.Enqueue(new TaskPack(id, callback));} }});//执行while (true){if (taskPackQuene.Count > 0){TaskPack taskPack;lock (obj){taskPack = taskPackQuene.Dequeue();}taskPack.callback(taskPack.id);}}}}
}//任务包
class TaskPack
{public int id;public Action<int> callback;public TaskPack(int id, Action<int> callback ){this.callback = callback;this.id = id;}
}
效果
310 增加一些常用API(时间)
PETime
#region ToolByTime///<summary>获取当前时间<para /></summary>public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}/// <summary>本地时间<para /></summary>public double GetMillSecondsTime(){nowTime= GetUTCMillSeconds();return nowTime;}/// <summary>本地时间<para /></summary> public DateTime GetDateTime(){//方法一、异常卡断不会一直运行DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds( nowTime));//方法二、DateTime.Now;异常卡断会一直运行return dateTime;}public int GetYear(){return GetDateTime().Year;}public int GetMonth(){return GetDateTime().Month;}public int GetWeek(){return (int)GetDateTime().DayOfWeek;}public int GetDay(){return GetDateTime().Day;}public string GetLocalTimeString(){DateTime dateTime = GetDateTime();string dateTimeString = GetTimeString(dateTime.Hour)+",";dateTimeString += GetTimeString(dateTime.Minute) + ",";dateTimeString += GetTimeString(dateTime.Second);return dateTimeString;}public string GetTimeString(int time){if (time == 0)return "0" + time.ToString();elsereturn time.ToString();}#endregion
Program
static void TimeTest(){PETimer pt = new PETimer(50);Console.WriteLine(pt.GetUTCMillSeconds().ToString());Console.WriteLine(pt.GetMillSecondsTime().ToString());Console.WriteLine(pt.GetDateTime().ToString());Console.WriteLine(pt.GetYear().ToString());Console.WriteLine(pt.GetMonth().ToString());Console.WriteLine(pt.GetWeek().ToString());Console.WriteLine(pt.GetDay().ToString());Console.WriteLine("\n");}
效果
//当时我没有定义double nowTime,直接用GetUTCMillSeconds(),第二条发生溢栈错误。重新定义了一个nowTime,没报错,如下图。
//报溢栈的代码,但是当我定义一次nowTime后再改回去,错误没有出现了
//
public double GetUTCMillSeconds()//计算时间间隔{/**DateTime.Now本地时间,中国东八区DateTime.UtcNow时间标准时间实际服务器标准时间,到具体国家在进行本地偏移**/TimeSpan timeSpan = DateTime.UtcNow - startDateTime;return timeSpan.TotalMilliseconds;}/// <summary>本地时间<para /></summary>public double GetMillSecondsTime(){return GetUTCMillSeconds();}/// <summary>本地时间<para /></summary> public DateTime GetDateTime(){//方法一、异常卡断不会一直运行DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(GetMillSecondsTime()));//方法二、DateTime.Now;异常卡断会一直运行return dateTime;}
311 多线程数据安全处理1 锁Add和加载,锁id
临时列表timeTmpList的添加和清除是多线程的
1、加锁,性能低
2、锁在一个临时列表。临时列表的Add频率较低
id的我一开始就没有设id列表。视频是把Add放在生成id的最后面。在回收的时候锁id
Time
LoadTaskTmpList()的频率比AddTimeTask高,
LoadTaskTmpList()通过if及时return,规避锁
//
Frame的也如上草最
private static readonly string lockTime = "lockTime";private static readonly string lockFrame = "lockTime";public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{delay = UnitConversion(delay, unit);////int id = GenerateId();PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);//taskTmpList.Add(task);return id;}void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印输出,所以returnfor (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}
修改后
public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒{delay = UnitConversion(delay, unit);////int id = GenerateId();PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);//lock (lockTime){taskTmpList.Add(task);}return id;}/// <summary>加载缓存的临时列表<para /></summary>void LoadTaskTmpList(){if (taskTmpList.Count <= 0) return;//一直打印输出,所以returnlock (lockTime){for (int i = 0; i < taskTmpList.Count; i++){taskList.Add(taskTmpList[i]);}taskTmpList.Clear();}}
锁id
312 多线程数据安全处理2 锁时间Delete
//有困惑的地方是for删除临时列表时,移除时,要不要j–;,解决移除后后面索引的前移
private List<int> taskDeleteTmpList; public void DeleteTimeTask(int id){lock (lockTime){taskDeleteTmpList.Add(id);Log("事件的删除临时列表的线程id:"+Thread.CurrentThread.ManagedThreadId.ToString());}}public void DeleteTimeTask(){if (taskDeleteTmpList.Count > 0){lock (lockTime){for (int j = 0; j < taskDeleteTmpList.Count ; j++){bool isDeleted = false;for (int i = 0; i < taskList.Count; i++){if (id == taskList[i].id){isDeleted = true;taskDeleteTmpList.RemoveAt(j);j--;taskList.RemoveAt(i);break;}}if (isDeleted){continue;}else{for (int i = 0; i < taskTmpList.Count; i++){if (id == taskTmpList[i].id){taskDeleteTmpList.RemoveAt(j);j--;taskTmpList.RemoveAt(i);break;}}}}taskDeleteTmpList.Clear(); }}}public void Update(){......DeleteTimeTask();......
313 多线程数据安全处理3 锁帧Delete 锁帧Id
跟上面一样的操作
但也是没有视频的idList
public void DeleteFrameTask(int id){lock (lockFrame){frame_taskDeleteTmpList.Add(id);Log("事件的删除临时列表的线程id:" + Thread.CurrentThread.ManagedThreadId.ToString());}}private void DeleteFrameTask(){if (frame_taskDeleteTmpList.Count > 0){lock (lockFrame){for (int j = 0; j < frame_taskDeleteTmpList.Count; j++){bool isDeleted = false;for (int i = 0; i < frame_taskList.Count; i++){if (id == frame_taskList[i].id){isDeleted = true;frame_taskDeleteTmpList.RemoveAt(j);frame_taskList.RemoveAt(i);break;}}if (isDeleted){continue;}else{for (int i = 0; i < frame_taskTmpList.Count; i++){if (id == taskTmpList[i].id){frame_taskDeleteTmpList.RemoveAt(j);frame_taskTmpList.RemoveAt(i);break;}}}}frame_taskDeleteTmpList.Clear();}}}
314 多线程数据安全处理4 测试Delete 整合成一个文件
//input=Console.ReadLine(),所以要按d后快速回车
Program
static void FinalTest(){Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();PETimer pt = new PETimer(50);pt.SetLog((string log) => { Console.WriteLine(log); });int id=pt.AddTimeTask((int id) =>{Console.WriteLine("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());},1000d, //执行AddTimeTask的时间间隔PETimeUnit.MillSecond,0);//执行while (true){ pt.Update();Console.WriteLine("while中的id"+id);string input = Console.ReadLine();if (input == "d"){pt.DeleteTimeTask(id);Console.WriteLine("删除");}if (taskPackQuene.Count > 0){TaskPack taskPack;lock (obj){taskPack = taskPackQuene.Dequeue();}taskPack.callback(taskPack.id);}}}
效果
Frame
相应修改
315 课程总结回顾
Unity 定时回调系统技术专题(Siki Plane)相关推荐
- 分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)
分布式定时任务调度系统技术解决方案(xxl-job.Elastic-job.Saturn) 参考文章: (1)分布式定时任务调度系统技术解决方案(xxl-job.Elastic-job.Saturn) ...
- 遇见未知的Saturn |准备篇:分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)
1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...
- quarts集群 运维_分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)...
1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...
- 分布式定时任务调度系统技术选型
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:EFbiz blog.csdn.net/guyue35/ar ...
- 分布式定时任务调度系统技术选型--转
http://www.expectfly.com/2017/08/15/%E5%88%86%E5%B8%83%E5%BC%8F%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1% ...
- 定时修改列表 服务器版,Unity定时回调(服务端不依赖Update)
服务器的话就没有Update了 所以我们要吧计时器从Mono和他自带的计时方法剥离出来 管理类只做一些简单的调用 启动Update using System.Collections; using Sy ...
- Java 后端开发面试总结:25 个技术专题(最全面试攻略)
另送福利: java 面试准备 准确的说这里又分为两部分: 1.Java 刷题 2.算法刷题 Java 刷题:此份文档详细记录了千道面试题与详解: ! 私信我回复[03]即可免费获取 很多人 ...
- 六大主题报告,四大技术专题,AI开发者大会首日精华内容全回顾
9月6-7日,2019中国AI开发者大会(AI ProCon 2019) 在北京拉开帷幕.本次大会由新一代人工智能产业技术创新战略联盟(AITISA)指导,鹏城实验室.北京智源人工智能研究院支持,专业 ...
- 明日开播 | 7 场不可错过的 AI 技术专题
如今人工智能已不单单是发表学术论文.刷新正确率的竞赛,抑或全民参与的新闻事件,它早在为各行各业的先行者们创造着实实在在的利润和商业价值.而且,随着算法改进.硬件升级.架构优化,应用人工智能技术带来的收 ...
最新文章
- 最全面的homogeneous单应性坐标的定义,以及不同投影,仿射,相似,刚体变换矩阵的关系和自由度分析
- bos 获取数据库连接_java解析数据接口获取json对象
- 两道JVM面试题,竟让我回忆起了中学时代!
- 曹大,欧神开新公众号了
- KNN针对中文文本分类
- devops 开发_DevOps如何消除开发瓶颈
- linux配置内存buffer,Linux中内存buffer和cache的区别
- Required String parameter 'images' is not present
- 【体系结构】buffer cache的个人理解
- Mac下docker安装kali/ubuntu14.04
- DNN永日新闻模块(YongRi)免费1.00.09版本下载
- Python3 发票导出XML转Excel
- 电力系统如何实现时间同步
- ae去闪插件deflicker使用_AE去闪烁插件|RevisionFX DEFlicker(AE视频去闪烁插件) V1.4.12 官方版 下载_当下软件园_软件下载...
- 网易有道词典去广告版
- 64位Ubuntu14.04系统无法解压bin文件的解决方法
- 算力网络走向智能社会,云计算初心未改(一)
- MacOS下Go语言环境搭建
- 汉字转拼音和简拼工具类分享
- 自学Java的心路历程
热门文章
- 蝉花与冬虫夏草的比较
- 全网最强Fiddler抓包实战教程(Android+IOS超级全面图文) 越来越刑
- ThinkPHP 路由使用
- 关于image-patchs论文中的指标FPR95的一些理解
- PyTorch如何获得显卡 Compute Compatibility
- 44_Pandas将分类变量转换为虚拟变量(get_dummies)
- 兰州市2021高考成绩怎么查询,兰州2021高考成绩排名榜单,兰州各高中高考成绩喜报...
- 探索职业:如何用英语询问别人的职业
- 如何念好ldquo;开放平台rdquo;这本经?
- 【网络爬虫】【java】微博爬虫(一):小试牛刀——网易微博爬虫(自定义关键字爬取微博数据)(附软件源码)...