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自帶的腳本執行順序(針對不同腳本)
不同物體下同一個腳本執行順序
//第一次從下到上
//后面再調整物體順序,不便了
//作者推薦的,用一個父節點來管理
(問題) 學習時腳本分類
到網頁復制標題
到VS利用Alt鍵盤修改標題(一般不允許加空格,如下下圖是不行的)
MD、bat(另存為ASNI編碼,默認的UTF8是會亂碼)
“靈者更名”添加空格
//MD 新建文件夾
104 理解Unity主線程設計思想1
//一個線程,主線程,串行運行
//邏輯幀,一個物體從Update到下一次Update的時間
105 理解Unity主線程設計思想2(線程ID)
//不允許在主線程之外訪問transform,限制編程環境單線程
//底層運用線程池,不需要開發者管理
106 協程的常規使用1(開啟協程的兩種方式的區別)
//第一種調用參數上限沒有限制
//第一種調用參數上限為1
107 協程的常規使用2(終止協程)
問答
//我也測試到StopCoroutine(A()); 對 StartCoroutine(A()); 無效
//視頻也講到StopCoroutine(A()); 對 StartCoroutine(“A”); 無效
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像一堵墻,執行順序如下。方框處是整個協程徹底結束的時候
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();}現在封裝成方法
//加一個按鈕事件
//單例采用封裝方法,一次控制順序,防止單例后運行
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();
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的調用相應調整
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;}#endregionGameRoot_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
//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,0309 增加任務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();}#endregionProgram
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后再改回去,錯誤沒有出現了
//
311 多線程數據安全處理1 鎖Add和加載,鎖id
臨時列表timeTmpList的添加和清除是多線程的
1、加鎖,性能低
2、鎖在一個臨時列表。臨時列表的Add頻率較低
id的我一開始就沒有設id列表。視頻是把Add放在生成id的最后面。在回收的時候鎖id
Time
LoadTaskTmpList()的頻率比AddTimeTask高,
LoadTaskTmpList()通過if及時return,規避鎖
//
Frame的也如上草最
修改后
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
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)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Python获取春节档电影影评,制作
- 下一篇: java信息管理系统总结_java实现科