我正在开发一个控制台应用程序,它会定期执行两个线程:一个用于 tickEvent 函数,另一个用于更新控制台显示。目前,每个线程只是在条件检查时阻塞,直到下一个周期到来。
这种方法有效,但对处理器的要求很高,而且对于我在后台运行的应用程序来说,会导致风扇发出很大的噪音。
当前方法:
static void HandleTickBehaviour()
{
Stopwatch tickDuration = new(); //time tick starts
while (true)
{
tickDuration.Restart();
Tick.Invoke(ElapsedTicks); // invoke all tick event watchers
Profiler.CalcTime.Add(tickDuration.ElapsedMilliseconds); // how long it took for all tick event to run
long targetThreadDelay = (int)(Math.Max(25 - Math.Max(_deltaTime - 25, 0), 0) / 1000d * Stopwatch.Frequency); // how long to wait for next tick (25ms - delta time)
while (true) if (tickDuration.ElapsedTicks >= targetThreadDelay) break;
_deltaTime = tickDuration.ElapsedMilliseconds;
Profiler.TickTime.Add(_deltaTime);
ElapsedTicks++;
}
}
internal static void HandleConsoleRender()
{
Stopwatch frameDuration = new();
while (true)
{
frameDuration.Restart();
RenderConsole();
while (true) if (frameDuration.ElapsedTicks >= displayWindowsTickInterval) break;
Profiler.RenderTime.Add(frameDuration.ElapsedTicks);
}
}
我尝试实现的另一种方法是使用thread.sleep()
,但是由于这些函数每隔几毫秒就会被调用一次,因此重新唤醒线程的延迟实在太多,甚至尝试解释它也会导致更新无法预测地发生。
要定期执行任何操作,通常的解决方案是使用计时器。市面上有多个计时器可供选择,但默认情况下,所有计时器的分辨率均为 15.6 毫秒。Thread.Sleep 也包含在内,因为它使用相同的底层机制。您观察到的数字非常接近该值的两倍,大致符合我的预期。这并不意味着 Thread.Sleep 有 5 毫秒的开销,只是意味着时间将被量化为 15.6 毫秒的倍数。
除非您正在做一些对延迟非常敏感的事情(例如音频/视频),否则这个分辨率通常就足够了,最简单的解决方案就是使用普通计时器,例如周期计时器,坚持 Windows 默认滴答率,并修复您遇到的任何“不可预测性”问题。
有一些方法可以获得更高分辨率的计时器,硬件应该能够至少达到 100us 的分辨率,但据我所知,.Net API 中尚未提供此功能,因此您需要使用 P/Invoke 来调用原生 Windows API。请参阅高分辨率计时器。还有一些“多媒体计时器”似乎使用稍旧的 API 来实现基本相同的功能。遗憾的是,我对此不太熟悉,无法提供完整的实现,但可能有可用的库。HighPrecisionTimer是 Google 推荐的,但我个人没有使用过。
我还会考虑线程通信的方式。目前没有同步机制,我假设预期的行为是渲染循环在每个循环执行时执行一次。为此,您可以使用BlockingCollection或手动重置事件。此外,还有一些流水线库,例如 DataFlow 或 Channels,可能对您尝试执行的操作有所帮助。
这是一个经典的编程示例
async
。具体来说,你想使用 aPeriodicTimer
并 await 每次 tick(这样就不会占用任何 CPU)。例如: