dotnet 读 WPF 源代码 聊聊 DispatcherTimer 的实现 您所在的位置:网站首页 graphpad怎么使用x dotnet 读 WPF 源代码 聊聊 DispatcherTimer 的实现

dotnet 读 WPF 源代码 聊聊 DispatcherTimer 的实现

2023-04-18 07:55| 来源: 网络整理| 查看: 265

本文来告诉大家在 WPF 框架里面,是如何实现 DispatcherTimer 的功能。有小伙伴告诉我,读源代码系列的博客看不动,原因是太底层了。我尝试换一个方式切入逻辑,通过提问题和解决问题的方法,一步步告诉大家 WPF 是如何实现 DispatcherTimer 的功能

假定咱是 WPF 框架的开发者(虽然我就是,尽管是格式化代码工程师)咱需要实现一个 DispatcherTimer 的功能,请问可以如何写呢

在 Windows 上有很多方式来实现计时器的功能,但是 DispatcherTimer 和其他的计时器有一点不同的在于,毕竟这是 Dispatcher 的,看到 Dispatcher 就可以了解到,这是一个需要在主线程执行的定时器

在那么如何在定时器里面回到主线程呢?假定咱现在啥都没有,毕竟咱现在是在从零开发 WPF 框架的,那有什么可以使用呢?在 Windows 上提供了 SetTimer 这个放在 User32.dll 的函数,通过这个 Win32 方法可以调用 Windows 提供的底层定时器的功能

写过 Win32 代码的小伙伴就知道,如果直接使用 Win32 的方法,无论是参数还是需要了解的知识都是非常多的。作为一个有追求的框架,咱肯定是需要再做一层封装,让调用更加简单。回到 SetTimer 这个 Win32 函数的功能上,咱可以调用 SetTimer 给定一个窗口句柄以及计时的时间,接下来 Windows 将会定时发送 WM_Timer 给到咱的窗口

假定咱已经有了接收窗口消息的统一入口,接受窗口调度的模块的功能就是调度执行,也就是 Dispatcher 的一个功能。那不妨就将 WM_Timer 的处理也放在 Dispatcher 里面吧。刚好咱选用的 SetTimer 是发送窗口消息,自然就是被主线程收到了,咱也就不需要去尝试解决后台线程的计时器需要调度到主线程

对于上层的 API 封装呢?给开发者使用的计时器肯定是需要封装一个类,那就叫 DispatcherTimer 好了。至于 DispatcherTimer 里面有哪些 API 呢,就抄 WPF 的设计好了

这里有一个问题是,假定我使用的是 DispatcherTimer 有多个,我使用其中的一个 DispatcherTimer 通过 SetTimer 这个 Win32 函数进行定时,在 Dispatcher 收到 WM_Timer 消息时,如果知道是需要调用哪个 DispatcherTimer 来执行?

通过分析需求,事实上这个问题不好解决,因为 Win32 的 WM_Timer 消息是不会告诉咱这个消息是被哪个逻辑调用的 SetTimer 方法调用的,不能通过 WM_Timer 获取 DispatcherTimer 对象

但是从需求分析,其实咱不需要关注收到消息对应的是哪个 DispatcherTimer 对象,因为 DispatcherTimer 对象的功能是执行 Tick 事件,而只要是时间刚好到达,就需要执行 Tick 事件了。为了实现此功能,咱也就需要有一个集合用来管理当前主线程所有的 DispatcherTimer 对象,用来了解在收到 WM_Timer 需要调用的 DispatcherTimer 对象有哪些

这个 DispatcherTimer 集合为了方便调用管理,不妨先放在 Dispatcher 类里面,毕竟一个线程就刚好有一个 Dispatcher 对象

public sealed class Dispatcher { private List _timers = new List(); internal void AddTimer(DispatcherTimer timer) { lock(_instanceLock) { // 忽略代码 _timers.Add(timer); } // 忽略代码 } }

那在啥时候需要调用 AddTimer 呢?在 DispatcherTimer 对象创建的时候?如果我只是创建一个空的 DispatcherTimer 对象,这个对象啥都不干,好像加入到 Dispatcher 的 _timers 也不合适。不如就在 DispatcherTimer 启动的时候添加

public class DispatcherTimer { public void Start() { lock(_instanceLock) { if(!_isEnabled) { _isEnabled = true; _dispatcher.AddTimer(this); } } } private Dispatcher _dispatcher; private bool _isEnabled; // 忽略代码 }

在收到 WM_Timer 事件,就需要 Dispatcher 去遍历所有的 DispatcherTimer 对象,看哪个对象当前需要被执行了。为了了解哪个 DispatcherTimer 需要被执行,就需要让 DispatcherTimer 记录两个信息,一个是距离下次执行的时间和调用执行 Start 函数的时间。通过判断调用 Start 的时间加上距离下次执行的时间是否小于或等于当前的时间,就可以判断当前的 DispatcherTimer 是否需要执行

咱来加一点代码在 DispatcherTimer 里面,在启动时记录时间

public void Start() { lock(_instanceLock) { if(!_isEnabled) { _isEnabled = true; _dispatcher.AddTimer(this); _startTime = DateTime.Now; } } } private DateTime _startTime; private TimeSpan _interval;

作为一个追求性能的框架,自然咱需要在每个地方都追求一下性能,例如获取当前时间,是否有更快的方法?通过 Environment.TickCount 属性可以获取更快的时间,使用 Environment.TickCount 获取的是毫秒数,表示的是开机到当前的时间,相对来说抽象一点,不过也刚好不会受到用户修改当前系统时间的影响,自然也就更稳定一些啦

既然都使用 Environment.TickCount 了,不如将 判断调用 Start 的时间加上距离下次执行的时间 合在一起计算吧,这样后续每次 WM_Timer 消息过来的时候,就不用每次都做一次加法了,直接判断值的大小即可

public void Start() { lock(_instanceLock) { if(!_isEnabled) { _isEnabled = true; _dispatcher.AddTimer(this); // 如果只是记录当前调用 Start 方法的时间,也就是 Environment.TickCount 时间。那么后续收到 WM_Timer 消息,都需要判断当前时间加上 _interval 的时间之后是否小于等于当前的时间。而这个加法计算是每次都需要调用的,为了性能优化,不如一开始就加上,后续就只需要判断大小 _dueTimeInTicks = Environment.TickCount + (int) _interval.TotalMilliseconds; } } } // 删除 DateTime 的定义,因为获取的性能不够,而且用户也许修改系统时间 // private DateTime _startTime; private TimeSpan _interval; // 用这个代替 DateTime 的方法,单位是毫秒。其实字段从规范来说是不应该 internal 公开的,然而在 WPF 里面,古老的开发者为了减少改动就公开了这个字段 internal int _dueTimeInTicks; // used by Dispatcher

在 Dispatcher 里面就可以通过 DispatcherTimer 的 _dueTimeInTicks 字段和当前的时间比较大小而决定是否触发 DispatcherTimer 的事件。从规范的角度来说,是不能公开 DispatcherTimer 的 _dueTimeInTicks 字段的,然而在 WPF 里面,古老的开发者为了减少改动就公开了这个字段

在 Dispatcher 里面的代码如下

public sealed class Dispatcher { private List _timers = new List(); private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { WindowMessage message = (WindowMessage) msg; // 忽略代码 if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_TIMERS) { // 忽略代码 PromoteTimers(Environment.TickCount); } } internal void PromoteTimers(int currentTimeInTicks) { DispatcherTimer timer = null; int iTimer = 0; var timersVersion = _timersVersion; do { lock(_instanceLock) { timer = null; // If the timers collection changed while we are in the middle of // looking for timers, start over. if(timersVersion != _timersVersion) { // 如果在循环过程,有其他逻辑加入了 _timers 的元素,意味着 _timers 的数量变更了 // 需要重新开始 timersVersion = _timersVersion; iTimer = 0; } while(iTimer < _timers.Count) { // WARNING: this is vulnerable to wrapping if(_timers[iTimer]._dueTimeInTicks - currentTimeInTicks


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有