深入理解WPF DispatcherTimer定时器机制
本文将详细探讨WPF中DispatcherTimer的几个核心问题,帮助开发者更好地理解其内部工作机制。
- DispatcherTimer的实际用途以及与Dispatcher的关联性
- 为什么构造函数不允许使用DispatcherPriority.Inactive优先级
- DispatcherTimer实现定时任务的具体原理
一、DispatcherTimer的作用机制及其与Dispatcher的关系
在WPF应用程序中,Dispatcher可以理解为UI线程的任务调度中心,负责协调和管理各种并发任务。UI线程需要处理众多不同类型的任务,包括用户输入响应、界面渲染更新、控件事件回调等,这些任务的重要程度各不相同。
DispatcherTimer的核心功能就是创建一个具有特定优先级的定时任务,并将其投入到Dispatcher的任务队列中。当设定的时间间隔(Interval)到期后,该任务才会被Dispatcher考虑执行。需要特别指出的是,Dispatcher仅能保证在时间间隔到达之前不会处理该定时任务,而具体何时执行,则取决于任务本身的优先级以及当前队列中的其他任务状态。
二、为何不能使用DispatcherPriority.Inactive优先级
首先观察DispatcherPriority枚举的定义:
public enum DispatcherPriority
{
Invalid = -1,
Inactive = 0,
SystemIdle = 1,
ApplicationIdle = 2,
ContextIdle = 3,
Background = 4,
Input = 5,
Loaded = 6,
Render = 7,
DataBind = 8,
Normal = 9,
Send = 10
}
从上述枚举定义可以看出,数值越大表示优先级越高。在实例化DispatcherTimer时,构造函数支持传入DispatcherPriority参数(默认值为DispatcherPriority.Background)。如果尝试传入DispatcherPriority.Inactive,代码会抛出异常。
让我们通过类图来理解DispatcherTimer的结构:
图1 DispatcherTimer核心类结构
启动DispatcherTimer有两种方式:将IsEnabled属性设置为true,或者直接调用Start()方法。两种方式最终都会触发内部方法TimerRestart的执行。让我们查看相关源码实现:
private void TimerRestart()
{
lock (_timerSyncObject)
{
// 防止重复启动
if (_pendingOperation != null)
{
return;
}
// 将任务以Inactive优先级加入调度队列
_pendingOperation = _uiDispatcher.BeginInvoke(
DispatcherPriority.Inactive,
new DispatcherOperationCallback(ExecuteTickCallback),
null);
// 计算到期时间点
_expirationTicks = Environment.TickCount + (int) _interval.TotalMilliseconds;
// 判断是否需要提升优先级
if (_interval.TotalMilliseconds == 0 && _uiDispatcher.CheckAccess())
{
UpgradePriority();
}
else
{
_uiDispatcher.RegisterTimer(this);
}
}
}
分析上述代码可以发现,TimerRestart方法调用BeginInvoke时使用的是Inactive优先级。这是为了什么呢?我们需要深入了解Dispatcher的任务调度策略。
| Dispatcher任务调度机制 --------------- Dispatcher与创建它的线程紧密关联:每个UI线程必须拥有至少一个Dispatcher实例,而每个Dispatcher也固定在特定线程上运行(通常就是创建该Dispatcher的线程)。 Dispatcher按照任务的重要程度将任务划分为三个层次: 1. 前台任务(高优先级):范围从DispatcherPriority.Loaded到DispatcherPriority.Send 2. 后台任务(中优先级):范围从DispatcherPriority.Background到DispatcherPriority.Input 3. 空闲任务(低优先级):范围从DispatcherPriority.SystemIdle到DispatcherPriority.ContextIdle Dispatcher在处理任务前会验证任务优先级是否在有效范围内,对于范围外的优先级将直接忽略。 |
接下来分析UpgradePriority方法的实现:
internal void UpgradePriority()
{
lock (_timerSyncObject)
{
if (_pendingOperation != null)
{
_pendingOperation.Priority = _userPriority;
}
}
}
这个方法看似简单,实际上却触发了一系列复杂的内部机制。当修改任务优先级时,会同时唤醒Dispatcher的消息处理循环,进而向Windows系统发送消息。系统收到消息后立即通知Dispatcher,Dispatcher随即从队列中取出优先级最高的任务进行处理。为了确保不会遗漏其他待处理任务,Dispatcher在取出任务的同时会继续向系统发送消息,形成一个循环处理机制。
处理流程可参考下图:
然而,UpgradePriority方法的执行需要满足特定条件(时间间隔为零且在同一线程上)。如果不满足条件,系统会调用RegisterTimer方法来处理。
RegisterTimer方法属于Dispatcher的内部实现,其主要功能是将当前Timer实例添加到Dispatcher维护的定时器集合中,并向Windows发送定时器设置消息。
此时存在一个问题:DispatcherTimer任务的优先级仍然是Inactive,按照常理不会被处理。但实际上,当Dispatcher接收到定时器相关消息后,会检查所有已到达时间间隔的定时器,并将这些定时器的任务提升到待处理队列中。这样就确保了定时任务能够被及时执行。(这说明Inactive是DispatcherTimer的内部保留优先级,仅作为占位使用。)
具体流程如图所示:
需要强调的是,UpgradePriority方法仅在定时器时间间隔到达后才会被调用。如果多个定时器同时到达时间间隔,Dispatcher会按照队列顺序依次处理。可以通过实验验证:创建两个优先级不同但间隔相同的定时器,让高优先级定时器执行耗时操作,观察低优先级定时器的执行时机。
三、DispatcherTimer定时作业的实现原理
通过前文的分析,这个问题的答案已经很清晰:DispatcherTimer的定时功能完全依赖于Dispatcher的任务调度机制。Dispatcher只能保证在设定的时间间隔到达之前不会处理该定时任务,而具体的执行时机则由任务优先级和队列状态共同决定。
总结
本文通过源码分析介绍了DispatcherTimer和Dispatcher的工作原理。需要深入了解的读者可以自行查阅.NET源代码,推荐以下途径:
- 使用Resharper插件进行反编译
- 使用Reflector工具
- 访问.NET WPF开源仓库(https://github.com/dotnet/wpf)