浅谈 .NET 与 Qt Timer 实现

2021-07-27 10:18:36 浏览数 (1)

前两天刚好跟同学提起如何实现一个 Timer 。提到了 Kafka 的时间轮和 Go 语言的四叉堆实现。所以就看了下 .NET 是如何实现 Timer 的。

.NET Timer 分为两种,一种是 System.Windows.Threading.DispatcherTimer , 另外一种是System.Timers.Timer

System.Windows.Threading.DispatcherTimer

.NET Framework 相关源码路径:

  • SystemWindowsThreadingDispatcherTimer.cs
  • SystemWindowsThreadingDispatcher.cs
  • SystemWindowsThreadingDispatcherOperation.cs

简要实现原理:在每次新增 DispatcherTimer 的时候,都会将回调的委托存入 Dispatcher 中的 DispatcherOperation 优先队列里,但是优先级是最差的。然后将 Timer 本身存入当前 Dispatcher 的 Timer List 中。还有一个值得关注的是,时间间隔会加上系统运行时间 Environment.TickCount ,变成绝对时间保存下来,这是为了后边 WM_TIMER 到达之后,对比是否超时做准备。接下来就要关注 Dispatcher 了,当 Dispatcher 新增、删除、响应 Timer 事件以及 DispatcherTimer 调整时间间隔的时候,会调用 UpdateWin32Timer() , 这个方法会在当前 Dispatcher 的 Timer List 中检索最近要触发的 DispatcherTimer,如果当前没有调用过 SetTimer() 或者调用过的 SetTimer 时间间隔比当前最近要触发的长,就取时间间隔,调用 SetTimer()。当收到 WM_TIMER 消息之后,将根据程序运行时间,对比时间间隔,选出已经超时的 Timer,将之前提到的 DispatcherOperation 优先级提升,等到下一个消息循环来到时,回调 Operation 将会被从优先对列取出,并执行。

System.Timers.Timer

.NET Framework 相关源码路径:

  • servicestimerssystemtimersTimer.cs
  • systemthreadingtimer.cs
  • coreclrsrcvmcomthreadpool.cpp

简要实现原理:System.Timers.Timer 只是对 System.Threading.Timer 包装,所以实现上看 System.Threading.Timer 就好。这就不得不提到 System.Threading.Timer 中的 TimerQueue 。 这是存有 TimerQueueTimer 的双向队列。每增加一个 Timer 的时候,都会将一个 TimerQueueTimer 放入 TimerQueue 队列。同时调用运行时的 Native 的代码 AppDomainTimerNative::CreateAppDomainTimer() 。后边就是 Native 的代码逻辑了,具体细节不表了,简单理解就是在线程池中搞一个线程,在线程中调用 SleepEx() 阻塞线程,当线程走完之后触发回调,再调回 .NET 托管代码,找到 TimerQueueTimer ,再执行用户回调。

QTimer

相关源码路径:

  • qtbasesrccorelibkernelqeventdispatcher_win.cpp
  • qtbasesrccorelibkernelqtimer.cpp
  • qtbasesrccorelibkernelqobject.cpp

QTimer 的实现就比较简单了,当增加一个 QTimer 的时候,会在 QEventDispatcher 中调用 Win32 API,同时在 QObject 中将 TimerId 保存到 Vector 中。唯一的细节是,时间间隔在 20ms 以下或者指定 QTimerType 为 Qt::PreciseTimer 的 QTimer 会在底层调用 timeSetEvent() (源码注释中也提到了,虽然方法废弃了,但是精度还是高依旧使用),而其他的就调用 SetTimer() 方法。

谈谈 SetTimer

SetTimer() 的调用是有限制的。不管别人信不信,反正我是信了。这一点在 MSDN 中 SetTimer 的描述并没有,不过通过一些现象,以及网上的一些其他帖子可以得到认证。据 SO 上的一位吃瓜网友表示,SetTimer() 会创建用户对象(虽然这一点微软也没说过),而用户对象在系统中是有限制的(这一点是微软明确说过的),而用户对象的数量上限是在注册表中的,根据微软的文档指示应该是在: HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWindowsUSERProcessHandleQuota 我看了一下 x64 系统应该是在 HKEY_LOCAL_MACHINESOFTWAREWOW6432NodeMicrosoftWindows NTCurrentVersionWindowsUSERProcessHandleQuota 。默认数量是 10000 。

小结

分析过以上几种 Timer 的实现,就知道 .NET 的 Timer 还是做了一些微小的优化的。这也是为什么我跟同事说, 即使都是拿来做 Windows 桌面开发,.NET 框架的上限还是要比 Qt 高的原因。这大概是因为 .NET 本身从一开始就不是以桌面开发作为目标的,所以它更要考虑性能问题,但正因为如此,源码看起来比 Qt 就更为困难;而 Qt 这么实现,对一般的桌面应用来说,完全够用,代码也更容易看懂。虽然两者的实现在极端情况下都会拉闸,但是显然 Qt 的 Timer 实现会更快拉闸……

0 人点赞