我的解决方案

CodingKTimer

初识

为什么需要定时系统

Unity侧:

  1. 简化协程计时写法。
  2. 协程基于Monobehaviour来实现,而物体激活属性变化会导致中断调用。这可由定时器解决。
  3. 不需要依赖于unityAPI。

服务端侧:

​ 简化代码 => 支持多线程、指定线程。

三种定时器

TickTimer (高频高精度的毫秒级定时)

  • 支持多线程;
  • 不依赖 Unity引擎环境,可在客户端服务器使用;
  • 可使用外部循环驱动计时,也可使用驱动线程(内部new的)来执行;
  • 定时回调默认是线程池工作线程运行,也就是在驱动线程中运行,也可外部自己驱动Handle运行;

内部使用线程安全字典ConcurrentDictionary来存储Task,遍历过程中Remove无影响。

调用线程

线程可以使用新工作线程:

1
2
3
// 使用新线程:
timerThread = new Thread(new ThreadStart(StartTick));
timerThread.Start();

也可以在Unity的Update中调用计时器内部 UpdateTask() 方法,确定性地使用Unity主线程。

另外还有一种方式就是自己调用Handle,也可以确定性地使用外部线程(比如Unity主线程)来执行任务:

  1. 内部的新工作线程不再负责执行任务,而是对一个 线程安全队列 进行任务添加。

  2. 外部 调用HandleTask()方法 来执行 内部队列 中的任务。

AsyncTimer (大量并发任务的定时)

  • 支持多线程;
  • 不依赖 Unity引擎环境,可在客户端服务器使用;
  • 使用 async await异步语法计时,运行在线程池中;
  • 定时回调可以在驱动线程中运行,也可在外部 Handle 运行

时间修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
task.fixDelta = 0;

...

do
{
// 限次循环任务
--task.count;
++task.loopIndex;
// ③ 修正误差
int delay = (int)(task.delay + task.fixDelta);
if (delay > 0)
{
await Task.Delay(delay, task.ct);
}
// ① 计算出实际开销时间
TimeSpan ts = DateTime.UtcNow - task.startTime;
// ② 修正实际时间值 = 理论开销时间 - 实际开销时间
task.fixDelta = (int)(task.delay * task.loopIndex - ts.TotalMilliseconds);

CallBackTaskCB(task);
} while (task.count > 0);

由于delay的累加会出现问题(主要是因为执行代码本身需要时间),导致出现 真实运行时间 与 逻辑运行时间 有偏差,所以需要修正每次delay的值从而保证真实运行时间。

比如14:00:00起了个定时任务,delay为1小时,结果15:00:00的时候开始执行任务,任务本身花掉了10s,那么就变成14:00:10的时候去+了一小时的delay。下一次执行就变成16:00:10了而不是计划中的16:00:00。用delta修正即可。

FrameTimer (主要用于逻辑帧数的定时)

  • 只可在单线程当中运行;
  • 不依赖 Unity引擎环境,可在客户端服务器使用;
  • 只能由外部循环驱动计数;
  • 定时回调只可以在驱动线程中运行;

内部不创建线程计时,只能外部驱动。内部没有时间概念,完全不再计时只计数

在服务端中或和客户端中,按自己的执行帧来每次调用,调用一次算一帧。其实就相当于只有前面两种Timer的Handle方法。

delay不再是时间,而是帧数。