您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

提高WPF DispatcherTimer精度

寒冰屋 发布时间:2022-08-30 19:45:00 ,浏览量:2

目录

介绍

2个Ticks之间WPF需要的最短时间

增加用于DispatcherTimer的DispatcherPriority

为间隔选择一个现实的持续时间

使tick每100毫秒可靠地增加

  • 下载源 - 3.2 KB
介绍

我写了一个开源的WPF游戏MasterGrab,它已经成功运行了6年。在这个游戏中,人类玩家与多个机器人对战。最近,我想扩展游戏,让机器人可以相互玩耍。用户可以选择机器人每秒应该移动多少次,每次移动都显示在屏幕上。

我认为使用DispatcherTimer会很容易实现,它每x秒在WPF线程上引发一个Tick事件。此持续时间由DispatcherTimer.Interval控制。当我将它设置为100毫秒时,我注意到我在不规则的时间每秒只看到大约4次移动。因此,我开始研究其DispatcherTimer行为方式以及如何改进。

2个Ticks之间WPF需要的最短时间

那么,第一个问题是:DispatcherTimer能跑多快?为了在我的PC上测量它,我编写了一个WPF应用程序,它仅仅运行DispatcherTimer。XAML窗口仅包含一个名称为MainTextBlock的TextBlock:

using System;
using System.Text;
using System.Windows;
using System.Windows.Threading;

namespace WpfTimer {
  public partial class MainWindow: Window {
    DispatcherTimer timer;

    public MainWindow() {
      InitializeComponent();

      timer = new();
      timer.Interval = TimeSpan.FromMilliseconds(0);
      timer.Tick += Timer_Tick;
      timer.Start();
    }

    const int timesCount = 20;
    DateTime[] times = new DateTime[timesCount];
    int timesIndex;

    private void Timer_Tick(object? sender, EventArgs e) {
      times[timesIndex] = DateTime.Now;
      if (++timesIndex>=timesCount) {
        timer.Stop();
        var sb = new StringBuilder();
        var startTime = times[0];
        for (int i = 1; i < timesCount; i++) {
          var time = times[i];
          sb.AppendLine($"{(time - startTime):ss\\.fff} | 
                       {(int)(time - times[i-1]).TotalMilliseconds, 3:##0}");
        }
        MainTextBox.Text = sb.ToString();
      }
    }
  }
}

输出是:

00.021 |  21
00.021 |   0
00.021 |   0
00.021 |   0
...

对于每个tick,有一行。第一列显示这个tick发生后有多少seconds.milliseconds。第二列显示了此tick和上一个tick之间经过了多长时间。

由于我将Interval设置为0,因此在tick之间没有时间损失,除了第一个tick和第二个tick之间。显然,将Interval设置为0没有意义,我本以为会出现异常。

这是结果Interval = 1 millisecond:

00.192 | 192
00.215 |  22
00.219 |   3
00.235 |  15
00.471 | 236
00.600 | 128
00.743 | 142
00.764 |  21
00.935 | 170
01.239 | 303
01.326 |  87
01.628 | 302
01.894 | 266
02.210 | 316
02.375 | 164
02.435 |  60
02.527 |  92
02.658 | 131
02.685 |  26

我没想到Tick会每毫秒提高一次,但我惊讶地发现2个tick之间可能会经过300多毫秒,考虑到我的应用程序除了运行计时器之外什么都不做。

增加用于DispatcherTimer的DispatcherPriority

然后我注意到DispatcherTimer构造函数可以带一个DispatcherPriority参数,该参数似乎设置为Background,这意味着计时器只会运行一次“所有其他非空闲操作都完成”。

Name      Priority Description
Invalid         -1 This is an invalid priority.
Inactive         0 Operations are not processed.
SystemIdle       1 Operations are processed when the system is idle.
ApplicationIdle  2 Operations are processed when the application is idle.
ContextIdle      3 Operations are processed after background operations have completed.
Background       4 Operations are processed after all other non-idle operations are completed.
Input            5 Operations are processed at the same priority as input.
Loaded           6 Operations are processed when layout and render has finished but just 
                   before items at input priority are serviced. Specifically this is used 
                   when raising the Loaded event.
Render           7 Operations processed at the same priority as rendering.
DataBind         8 Operations are processed at the same priority as data binding.
Normal           9 Operations are processed at normal priority. This is the typical 
                   application priority.
Send            1o Operations are processed before other asynchronous operations. This is 
                   the highest priority.

我在我的游戏应用程序中尝试了我可以使用的最高优先级。显然,必须在显示下一步之前完成渲染。因此,让我们看看Tick使用DispatcherPriority.Input时提升的速度有多快:

00.014 |  14
00.024 |   9
00.091 |  67
00.202 | 111
00.221 |  19
00.226 |   4
00.242 |  16
00.272 |  30
00.307 |  34
00.369 |  61
00.460 |  91
00.493 |  33
00.524 |  30
00.555 |  31
00.586 |  30
00.712 | 125
00.745 |  33
00.761 |  15
00.788 |  27
为间隔选择一个现实的持续时间

我想说它现在可以快近3倍的速度运行。显然,Interval=1millisecond没有真正的意义。那么Interval=100 msec怎么样?

00.193 | 193
00.292 |  98
00.417 | 124
00.592 | 174
00.718 | 126
00.876 | 157
01.001 | 125
01.142 | 141
01.263 | 120
01.392 | 129
01.559 | 166
01.677 | 117
01.872 | 195
02.010 | 137
02.143 | 133
02.256 | 113
02.358 | 101
02.472 | 114
02.589 | 116

糟糕,现在2个tick之间所需的时间几乎总是显着超过100毫秒,而我每秒只得到大约7个而不是10个tick。:-( 问题似乎是在x毫秒的随机延迟后,计时器再次等待100毫秒,而不是100-x毫秒。

使tick每100毫秒可靠地增加

所以基本上,我们必须告诉在每个tick事件期间,Interval应该有多少毫秒长,直到下一个tick:

const int constantInterval = 100;//milliseconds

private void Timer_Tick(object? sender, EventArgs e) {
  var now = DateTime.Now;
  var nowMilliseconds = (int)now.TimeOfDay.TotalMilliseconds;
  var timerInterval = constantInterval - 
   nowMilliseconds%constantInterval + 5;//5: sometimes the tick comes few millisecs early
  timer.Interval = TimeSpan.FromMilliseconds(timerInterval);

代码是这样工作的。它试图每0.1秒准确地进行一次tick。因此,如果第一个tick发生在142毫秒之后,则Interval设置为58毫秒而不是100:

00.093 |  93
00.216 | 122
00.311 |  95
00.408 |  96
00.515 | 106
00.611 |  96
00.730 | 119
00.859 | 128
00.929 |  70
00.995 |  65
01.147 | 152
01.209 |  62
01.314 | 104
01.402 |  87
01.496 |  94
01.621 | 125
01.731 | 109
01.794 |  63
01.936 | 141

最后!现在我每秒有10个tick。当然,也不完全是每100毫秒,因为有时,WPF线程仍然需要太多时间来进行其他活动。但是当这种情况发生时,计时器至少会尝试更快地提高下一个tick。

  • https://github.com/PeterHuberSg/MasterGrab

https://www.codeproject.com/Articles/5323994/Improving-the-WPF-DispatcherTimer-Precision

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0469s