当前位置:   article > 正文

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

unity c#定时返回数据

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

实现功能

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

思路:

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

 需要注意的问题:

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

 核心代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Timers;
  4. public class PETimer {
  5. private Action<string> taskLog;
  6. private Action<Action<int>, int> taskHandle;
  7. private static readonly string lockTid = "lockTid";
  8. private DateTime startDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
  9. private double nowTime;
  10. private Timer srvTimer;
  11. private int tid;
  12. private List<int> tidLst = new List<int>();
  13. private List<int> recTidLst = new List<int>();
  14. private static readonly string lockTime = "lockTime";
  15. private List<PETimeTask> tmpTimeLst = new List<PETimeTask>();
  16. private List<PETimeTask> taskTimeLst = new List<PETimeTask>();
  17. private List<int> tmpDelTimeLst = new List<int>();
  18. private int frameCounter;
  19. private static readonly string lockFrame = "lockFrame";
  20. private List<PEFrameTask> tmpFrameLst = new List<PEFrameTask>();
  21. private List<PEFrameTask> taskFrameLst = new List<PEFrameTask>();
  22. private List<int> tmpDelFrameLst = new List<int>();
  23. public PETimer(int interval = 0) {
  24. tidLst.Clear();
  25. recTidLst.Clear();
  26. tmpTimeLst.Clear();
  27. taskTimeLst.Clear();
  28. tmpFrameLst.Clear();
  29. taskFrameLst.Clear();
  30. if (interval != 0) {
  31. srvTimer = new Timer(interval) {
  32. AutoReset = true
  33. };
  34. srvTimer.Elapsed += (object sender, ElapsedEventArgs args) => {
  35. Update();
  36. };
  37. srvTimer.Start();
  38. }
  39. }
  40. public void Update() {
  41. CheckTimeTask();
  42. CheckFrameTask();
  43. DelTimeTask();
  44. DelFrameTask();
  45. if (recTidLst.Count > 0) {
  46. lock (lockTid) {
  47. RecycleTid();
  48. }
  49. }
  50. }
  51. private void DelTimeTask() {
  52. if (tmpDelTimeLst.Count > 0) {
  53. lock (lockTime) {
  54. for (int i = 0; i < tmpDelTimeLst.Count; i++) {
  55. bool isDel = false;
  56. int delTid = tmpDelTimeLst[i];
  57. for (int j = 0; j < taskTimeLst.Count; j++) {
  58. PETimeTask task = taskTimeLst[j];
  59. if (task.tid == delTid) {
  60. isDel = true;
  61. taskTimeLst.RemoveAt(j);
  62. recTidLst.Add(delTid);
  63. //LogInfo("Del taskTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
  64. break;
  65. }
  66. }
  67. if (isDel)
  68. continue;
  69. for (int j = 0; j < tmpTimeLst.Count; j++) {
  70. PETimeTask task = tmpTimeLst[j];
  71. if (task.tid == delTid) {
  72. tmpTimeLst.RemoveAt(j);
  73. recTidLst.Add(delTid);
  74. //LogInfo("Del tmpTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
  75. break;
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. private void DelFrameTask() {
  83. if (tmpDelFrameLst.Count > 0) {
  84. lock (lockFrame) {
  85. for (int i = 0; i < tmpDelFrameLst.Count; i++) {
  86. bool isDel = false;
  87. int delTid = tmpDelFrameLst[i];
  88. for (int j = 0; j < taskFrameLst.Count; j++) {
  89. PEFrameTask task = taskFrameLst[j];
  90. if (task.tid == delTid) {
  91. isDel = true;
  92. taskFrameLst.RemoveAt(j);
  93. recTidLst.Add(delTid);
  94. break;
  95. }
  96. }
  97. if (isDel)
  98. continue;
  99. for (int j = 0; j < tmpFrameLst.Count; j++) {
  100. PEFrameTask task = tmpFrameLst[j];
  101. if (task.tid == delTid) {
  102. tmpFrameLst.RemoveAt(j);
  103. recTidLst.Add(delTid);
  104. break;
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. private void CheckTimeTask() {
  112. if (tmpTimeLst.Count > 0) {
  113. lock (lockTime) {
  114. //加入缓存区中的定时任务
  115. for (int tmpIndex = 0; tmpIndex < tmpTimeLst.Count; tmpIndex++) {
  116. taskTimeLst.Add(tmpTimeLst[tmpIndex]);
  117. }
  118. tmpTimeLst.Clear();
  119. }
  120. }
  121. //遍历检测任务是否达到条件
  122. nowTime = GetUTCMilliseconds();
  123. for (int index = 0; index < taskTimeLst.Count; index++) {
  124. PETimeTask task = taskTimeLst[index];
  125. if (nowTime.CompareTo(task.destTime) < 0) {
  126. continue;
  127. }
  128. else {
  129. Action<int> cb = task.callback;
  130. try {
  131. if (taskHandle != null) {
  132. taskHandle(cb, task.tid);
  133. }
  134. else {
  135. if (cb != null) {
  136. cb(task.tid);
  137. }
  138. }
  139. }
  140. catch (Exception e) {
  141. LogInfo(e.ToString());
  142. }
  143. //移除已经完成的任务
  144. if (task.count == 1) {
  145. taskTimeLst.RemoveAt(index);
  146. index--;
  147. recTidLst.Add(task.tid);
  148. }
  149. else {
  150. if (task.count != 0) {
  151. task.count -= 1;
  152. }
  153. task.destTime += task.delay;
  154. }
  155. }
  156. }
  157. }
  158. private void CheckFrameTask() {
  159. if (tmpFrameLst.Count > 0) {
  160. lock (lockFrame) {
  161. //加入缓存区中的定时任务
  162. for (int tmpIndex = 0; tmpIndex < tmpFrameLst.Count; tmpIndex++) {
  163. taskFrameLst.Add(tmpFrameLst[tmpIndex]);
  164. }
  165. tmpFrameLst.Clear();
  166. }
  167. }
  168. frameCounter += 1;
  169. //遍历检测任务是否达到条件
  170. for (int index = 0; index < taskFrameLst.Count; index++) {
  171. PEFrameTask task = taskFrameLst[index];
  172. if (frameCounter < task.destFrame) {
  173. continue;
  174. }
  175. else {
  176. Action<int> cb = task.callback;
  177. try {
  178. if (taskHandle != null) {
  179. taskHandle(cb, task.tid);
  180. }
  181. else {
  182. if (cb != null) {
  183. cb(task.tid);
  184. }
  185. }
  186. }
  187. catch (Exception e) {
  188. LogInfo(e.ToString());
  189. }
  190. //移除已经完成的任务
  191. if (task.count == 1) {
  192. taskFrameLst.RemoveAt(index);
  193. index--;
  194. recTidLst.Add(task.tid);
  195. }
  196. else {
  197. if (task.count != 0) {
  198. task.count -= 1;
  199. }
  200. task.destFrame += task.delay;
  201. }
  202. }
  203. }
  204. }
  205. #region TimeTask
  206. public int AddTimeTask(Action<int> callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
  207. if (timeUnit != PETimeUnit.Millisecond) {
  208. switch (timeUnit) {
  209. case PETimeUnit.Second:
  210. delay = delay * 1000;
  211. break;
  212. case PETimeUnit.Minute:
  213. delay = delay * 1000 * 60;
  214. break;
  215. case PETimeUnit.Hour:
  216. delay = delay * 1000 * 60 * 60;
  217. break;
  218. case PETimeUnit.Day:
  219. delay = delay * 1000 * 60 * 60 * 24;
  220. break;
  221. default:
  222. LogInfo("Add Task TimeUnit Type Error...");
  223. break;
  224. }
  225. }
  226. int tid = GetTid(); ;
  227. nowTime = GetUTCMilliseconds();
  228. lock (lockTime) {
  229. tmpTimeLst.Add(new PETimeTask(tid, callback, nowTime + delay, delay, count));
  230. }
  231. return tid;
  232. }
  233. public void DeleteTimeTask(int tid) {
  234. lock (lockTime) {
  235. tmpDelTimeLst.Add(tid);
  236. //LogInfo("TmpDel ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
  237. }
  238. /*
  239. bool exist = false;
  240. for (int i = 0; i < taskTimeLst.Count; i++) {
  241. PETimeTask task = taskTimeLst[i];
  242. if (task.tid == tid) {
  243. //taskTimeLst.RemoveAt(i);
  244. for (int j = 0; j < tidLst.Count; j++) {
  245. if (tidLst[j] == tid) {
  246. //tidLst.RemoveAt(j);
  247. break;
  248. }
  249. }
  250. exist = true;
  251. break;
  252. }
  253. }
  254. if (!exist) {
  255. for (int i = 0; i < tmpTimeLst.Count; i++) {
  256. PETimeTask task = tmpTimeLst[i];
  257. if (task.tid == tid) {
  258. //tmpTimeLst.RemoveAt(i);
  259. for (int j = 0; j < tidLst.Count; j++) {
  260. if (tidLst[j] == tid) {
  261. //tidLst.RemoveAt(j);
  262. break;
  263. }
  264. }
  265. exist = true;
  266. break;
  267. }
  268. }
  269. }
  270. return exist;
  271. */
  272. }
  273. public bool ReplaceTimeTask(int tid, Action<int> callback, float delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
  274. if (timeUnit != PETimeUnit.Millisecond) {
  275. switch (timeUnit) {
  276. case PETimeUnit.Second:
  277. delay = delay * 1000;
  278. break;
  279. case PETimeUnit.Minute:
  280. delay = delay * 1000 * 60;
  281. break;
  282. case PETimeUnit.Hour:
  283. delay = delay * 1000 * 60 * 60;
  284. break;
  285. case PETimeUnit.Day:
  286. delay = delay * 1000 * 60 * 60 * 24;
  287. break;
  288. default:
  289. LogInfo("Replace Task TimeUnit Type Error...");
  290. break;
  291. }
  292. }
  293. nowTime = GetUTCMilliseconds();
  294. PETimeTask newTask = new PETimeTask(tid, callback, nowTime + delay, delay, count);
  295. bool isRep = false;
  296. for (int i = 0; i < taskTimeLst.Count; i++) {
  297. if (taskTimeLst[i].tid == tid) {
  298. taskTimeLst[i] = newTask;
  299. isRep = true;
  300. break;
  301. }
  302. }
  303. if (!isRep) {
  304. for (int i = 0; i < tmpTimeLst.Count; i++) {
  305. if (tmpTimeLst[i].tid == tid) {
  306. tmpTimeLst[i] = newTask;
  307. isRep = true;
  308. break;
  309. }
  310. }
  311. }
  312. return isRep;
  313. }
  314. #endregion
  315. #region FrameTask
  316. public int AddFrameTask(Action<int> callback, int delay, int count = 1) {
  317. int tid = GetTid();
  318. lock (lockTime) {
  319. tmpFrameLst.Add(new PEFrameTask(tid, callback, frameCounter + delay, delay, count));
  320. }
  321. return tid;
  322. }
  323. public void DeleteFrameTask(int tid) {
  324. lock (lockFrame) {
  325. tmpDelFrameLst.Add(tid);
  326. }
  327. /*
  328. bool exist = false;
  329. for (int i = 0; i < taskFrameLst.Count; i++) {
  330. PEFrameTask task = taskFrameLst[i];
  331. if (task.tid == tid) {
  332. //taskFrameLst.RemoveAt(i);
  333. for (int j = 0; j < tidLst.Count; j++) {
  334. if (tidLst[j] == tid) {
  335. //tidLst.RemoveAt(j);
  336. break;
  337. }
  338. }
  339. exist = true;
  340. break;
  341. }
  342. }
  343. if (!exist) {
  344. for (int i = 0; i < tmpFrameLst.Count; i++) {
  345. PEFrameTask task = tmpFrameLst[i];
  346. if (task.tid == tid) {
  347. //tmpFrameLst.RemoveAt(i);
  348. for (int j = 0; j < tidLst.Count; j++) {
  349. if (tidLst[j] == tid) {
  350. //tidLst.RemoveAt(j);
  351. break;
  352. }
  353. }
  354. exist = true;
  355. break;
  356. }
  357. }
  358. }
  359. return exist;
  360. */
  361. }
  362. public bool ReplaceFrameTask(int tid, Action<int> callback, int delay, int count = 1) {
  363. PEFrameTask newTask = new PEFrameTask(tid, callback, frameCounter + delay, delay, count);
  364. bool isRep = false;
  365. for (int i = 0; i < taskFrameLst.Count; i++) {
  366. if (taskFrameLst[i].tid == tid) {
  367. taskFrameLst[i] = newTask;
  368. isRep = true;
  369. break;
  370. }
  371. }
  372. if (!isRep) {
  373. for (int i = 0; i < tmpFrameLst.Count; i++) {
  374. if (tmpFrameLst[i].tid == tid) {
  375. tmpFrameLst[i] = newTask;
  376. isRep = true;
  377. break;
  378. }
  379. }
  380. }
  381. return isRep;
  382. }
  383. #endregion
  384. public void SetLog(Action<string> log) {
  385. taskLog = log;
  386. }
  387. public void SetHandle(Action<Action<int>, int> handle) {
  388. taskHandle = handle;
  389. }
  390. public void Reset() {
  391. tid = 0;
  392. tidLst.Clear();
  393. recTidLst.Clear();
  394. tmpTimeLst.Clear();
  395. taskTimeLst.Clear();
  396. tmpFrameLst.Clear();
  397. taskFrameLst.Clear();
  398. taskLog = null;
  399. srvTimer.Stop();
  400. }
  401. public int GetYear() {
  402. return GetLocalDateTime().Year;
  403. }
  404. public int GetMonth() {
  405. return GetLocalDateTime().Month;
  406. }
  407. public int GetDay() {
  408. return GetLocalDateTime().Day;
  409. }
  410. public int GetWeek() {
  411. return (int)GetLocalDateTime().DayOfWeek;
  412. }
  413. public DateTime GetLocalDateTime() {
  414. DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(nowTime));
  415. return dt;
  416. }
  417. public double GetMillisecondsTime() {
  418. return nowTime;
  419. }
  420. public string GetLocalTimeStr() {
  421. DateTime dt = GetLocalDateTime();
  422. string str = GetTimeStr(dt.Hour) + ":" + GetTimeStr(dt.Minute) + ":" + GetTimeStr(dt.Second);
  423. return str;
  424. }
  425. #region Tool Methonds
  426. private int GetTid() {
  427. lock (lockTid) {
  428. tid += 1;
  429. //安全代码,以防万一
  430. while (true) {
  431. if (tid == int.MaxValue) {
  432. tid = 0;
  433. }
  434. bool used = false;
  435. for (int i = 0; i < tidLst.Count; i++) {
  436. if (tid == tidLst[i]) {
  437. used = true;
  438. break;
  439. }
  440. }
  441. if (!used) {
  442. tidLst.Add(tid);
  443. break;
  444. }
  445. else {
  446. tid += 1;
  447. }
  448. }
  449. }
  450. return tid;
  451. }
  452. private void RecycleTid() {
  453. for (int i = 0; i < recTidLst.Count; i++) {
  454. int tid = recTidLst[i];
  455. for (int j = 0; j < tidLst.Count; j++) {
  456. if (tidLst[j] == tid) {
  457. tidLst.RemoveAt(j);
  458. break;
  459. }
  460. }
  461. }
  462. recTidLst.Clear();
  463. }
  464. private void LogInfo(string info) {
  465. if (taskLog != null) {
  466. taskLog(info);
  467. }
  468. }
  469. private double GetUTCMilliseconds() {
  470. TimeSpan ts = DateTime.UtcNow - startDateTime;
  471. return ts.TotalMilliseconds;
  472. }
  473. private string GetTimeStr(int time) {
  474. if (time < 10) {
  475. return "0" + time;
  476. }
  477. else {
  478. return time.ToString();
  479. }
  480. }
  481. #endregion
  482. class PETimeTask {
  483. public int tid;
  484. public Action<int> callback;
  485. public double destTime;//单位:毫秒
  486. public double delay;
  487. public int count;
  488. public PETimeTask(int tid, Action<int> callback, double destTime, double delay, int count) {
  489. this.tid = tid;
  490. this.callback = callback;
  491. this.destTime = destTime;
  492. this.delay = delay;
  493. this.count = count;
  494. }
  495. }
  496. class PEFrameTask {
  497. public int tid;
  498. public Action<int> callback;
  499. public int destFrame;
  500. public int delay;
  501. public int count;
  502. public PEFrameTask(int tid, Action<int> callback, int destFrame, int delay, int count) {
  503. this.tid = tid;
  504. this.callback = callback;
  505. this.destFrame = destFrame;
  506. this.delay = delay;
  507. this.count = count;
  508. }
  509. }
  510. }
  511. public enum PETimeUnit {
  512. Millisecond,
  513. Second,
  514. Minute,
  515. Hour,
  516. Day
  517. }

使用示例代码

PETimer控制台工程案例代码:

  1. using System;
  2. using System.Threading;
  3. using System.Collections.Generic;
  4. namespace ConsoleProjects {
  5. class Program {
  6. private static readonly string obj = "lock";
  7. static void Main(string[] args) {
  8. Console.WriteLine("Test Start!");
  9. //Test1();
  10. Test2();
  11. }
  12. //第一种用法:运行线程检测并处理任务
  13. static void Test1() {
  14. //运行线程驱动计时
  15. PETimer pt = new PETimer();
  16. pt.SetLog((string info) => {
  17. Console.WriteLine("LogInfo:" + info);
  18. });
  19. pt.AddTimeTask((int tid) => {
  20. Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
  21. }, 10, PETimeUnit.Millisecond, 0);
  22. while (true) {
  23. pt.Update();
  24. }
  25. }
  26. //第二种用法:独立线程检测并处理任务
  27. static void Test2() {
  28. Queue<TaskPack> tpQue = new Queue<TaskPack>();
  29. //独立线程驱动计时
  30. PETimer pt = new PETimer(5);
  31. pt.SetLog((string info) => {
  32. Console.WriteLine("LogInfo:" + info);
  33. });
  34. int id = pt.AddTimeTask((int tid) => {
  35. Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
  36. }, 3000, PETimeUnit.Millisecond, 0);
  37. //设置回调处理器
  38. /*
  39. pt.SetHandle((Action<int> cb, int tid) => {
  40. if (cb != null) {
  41. lock (obj) {
  42. tpQue.Enqueue(new TaskPack(tid, cb));
  43. }
  44. }
  45. });
  46. */
  47. while (true) {
  48. string ipt = Console.ReadLine();
  49. if (ipt == "a") {
  50. pt.DeleteTimeTask(id);
  51. }
  52. if (tpQue.Count > 0) {
  53. TaskPack tp = null;
  54. lock (obj) {
  55. tp = tpQue.Dequeue();
  56. }
  57. tp.cb(tp.tid);
  58. }
  59. }
  60. }
  61. }
  62. //任务数据包
  63. class TaskPack {
  64. public int tid;
  65. public Action<int> cb;
  66. public TaskPack(int tid, Action<int> cb) {
  67. this.tid = tid;
  68. this.cb = cb;
  69. }
  70. }
  71. }

PETimer集成到Unity案例代码:

  1. using UnityEngine;
  2. public class GameStart : MonoBehaviour {
  3. PETimer pt = new PETimer();
  4. int tempID = -1;
  5. private void Start() {
  6. //时间定时
  7. pt.AddTimeTask(TimerTask, 500, PETimeUnit.Millisecond, 3);
  8. //帧数定时
  9. pt.AddFrameTask(FrameTask, 100, 3);
  10. //定时替换/删除
  11. tempID = pt.AddTimeTask((int tid) => {
  12. Debug.Log("定时等待替换......");
  13. }, 1, PETimeUnit.Second, 0);
  14. }
  15. private void Update() {
  16. pt.Update();
  17. //定时替换
  18. if (Input.GetKeyDown(KeyCode.R)) {
  19. bool succ = pt.ReplaceTimeTask(tempID, (int tid) => {
  20. Debug.Log("定时等待删除......");
  21. }, 2, PETimeUnit.Second, 0);
  22. if (succ) {
  23. Debug.Log("替换成功");
  24. }
  25. }
  26. //定时删除
  27. if (Input.GetKeyDown(KeyCode.D)) {
  28. pt.DeleteTimeTask(tempID);
  29. }
  30. }
  31. void TimerTask(int tid) {
  32. Debug.Log("TimeTask:" + System.DateTime.UtcNow);
  33. }
  34. void FrameTask(int tid) {
  35. Debug.Log("FrameTask:" + System.DateTime.UtcNow);
  36. }
  37. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/108228
推荐阅读
相关标签
  

闽ICP备14008679号