WPF keyDown响应时间准确性

时间:2013-10-09 23:07:10

标签: c# .net wpf

我正在开发一个应用程序,用户可以看到某些内容,并且必须通过单击键盘上的键来做出反应。反应时间至关重要,越准确越好。

我只用几行代码编写了示例app inf WPF来测试默认设置:

namespace Test
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    private Stopwatch sw; 
    public MainWindow()
    {
        InitializeComponent();
        sw = new Stopwatch();
        sw.Start();
        this.KeyDown += OnKeyDown;
    }

    private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
    {
        sw.Stop();

        lbl.Content = sw.ElapsedMilliseconds;
        sw.Restart();
    }
  }
}

lbl是一个简单的标签。

奇怪的是,当我按下例如空格并按住它时,lbl的值会在范围内变化:30-33。

所以我无法预测响应准确度是多少?例如1毫秒精度是不可能的?用户同时点击空间(例如1毫秒精度)我可以在事件处理程序中处理它吗?

主要问题是:

假设我有一个按键事件处理程序:

Test_KeyDown(object sender, KeyEventArgs keyEventArgs)
{
   time = stopwatch.elapsed();
   stopwatch.Restart();
}

可能发生的“时间”的最小值是多少?我能确定时间值精确到1毫秒吗? 在这种方法中,我启动秒表但是我必须等待 - 多长时间 - 刷新GUI?

3 个答案:

答案 0 :(得分:11)

你的测试肯定是无效的,它只测量其他几个贡献者指出的键盘重复率。但它有意无益,你可以看到你的键盘没有问题。

Windows中的大多数事件都是以时钟中断率确定的速率发生的。默认情况下,每秒刻录64次,每15.625毫秒一次。这唤醒了内核并且它正在寻找是否需要完成某些事情,寻找传递给处理器核心的工作。最典型的是无所事事,核心是通过HLT指令关闭的。直到下一次中断发生。

所以关注的是你的测试永远不会比15.625毫秒更准确。而且你的观察结果恰巧与你所看到的相匹配的数字是两倍。但事实并非如此,您可以使用您的程序来查看。使用控制面板+键盘并调整“重复速率”滑块。请注意如何调整它并将数字更改为不是15.625的倍数的值。

这并非完全是一个意外,键盘控制器也会产生一个中断,就像时钟一样。你有充分的证据证明这个中断本身已经足够好,可以让你的程序重新激活。你可以看出键盘控制器本身足够快,扫描键盘矩阵。键盘上的错误栏不会大于+/- 2毫秒,大约是您在显示的数字中看到的噪音。如果你的键盘扫描速度较慢,你可以通过这个测试来消除它。


你所关注的更大的问题是视频。视频适配器通常以每秒60次更新刷新LCD监视器。因此,最糟糕的情况是,测试对象将无法在17毫秒内物理图像。液晶显示器本身也不是那么快,便宜的显示器的响应时间为16毫秒或更差。液体中的水晶的副作用不能足够快地翻转。

消除刷新率错误要求您编程与vertical blanking interval同步。你可以用DirectX做些什么。您可以找到响应时间约为4毫秒的高档液晶显示器,深受游戏玩家欢迎。

答案 1 :(得分:5)

首先,如果Stopwatch.IsHighResolutiontrue,则Stopwatch使用QueryPerformanceCounter,并且可以使用<1 ms分辨率来测量时间间隔。

其次,当您按住空格键时,Windows会重复开始发送WM_KEYDOWN消息,秒表将测量这些消息之间的间隔。此时间间隔由注册表项HKCU\Control Panel\Keyboard\KeyboardSpeed确定。

它的默认值是31,这是最快的重复率,这意味着每秒大约30个字符。这就是您测量大约1000/30 = 33 ms间隔的原因。

如果你把它设置为0,也就是说,设置为最慢的重复率,这意味着每秒大约2个字符,那么你应该测量大约。间隔500毫秒。我用这个设置测试了你的代码,我确实得到了500毫秒。 (更改KeyboardSpeed后别忘了重启Windows!)

您需要捕获单个keydown事件,而不是重复事件,因此您不必更改KeyboardSpeed设置。您的程序应该只向用户显示对象,启动秒表,并在发生keydown事件时停止它。 ElapsedMilliseconds将给出反应时间。多次测量并使用平均值。

问题是,即使QueryPerformanceCounter准确地测量经过的时间,键盘和Windows本身也会导致延迟,这将增加测量的反应时间。此外,延迟不是恒定的:如果Windows在应该处理keydown事件的那一刻忙,那么延迟将会更大。因此,如果你认真对待这个任务,你应该校准你的程序。

我的意思是,您应该购买或构建一个基于微控制器的小型电子设备,该设备打开LED并检测打开LED和用户按下按钮之间所经过的时间。使用此设备进行10-20(越多越好)反应时间测量,并与同一测试人员一起,使用您的程序再进行10-20次测量。两者之间的差异将给你键盘和Windows造成的延迟。可以从您的程序测量的反应时间中减去这种差异。

(有人可能会问,为什么你不应该使用小而精确的电子设备而不是Windows应用程序。首先,制造和销售软件比制造和销售硬件更容易和更便宜。其次,视觉对象可以复杂(例如棋盘格),复杂的对象可以在PC上更有效地呈现。)

答案 2 :(得分:1)

我想在此指出另一种追踪时机的工具。由于您正在考虑测试应用程序的响应,并且正如有人提到这涉及操作系统的消息,您可以利用Spy ++来查看这些消息的时间。我将按空格的输出复制到我一直在监听键盘消息的窗口,打开了所有输出。我按下了一次空间,然后尽可能快地在通过扩展坞的USB键盘上释放。你可以看到,需要大约0.55毫秒来处理。

<00001> 00090902 P WM_KEYDOWN nVirtKey:VK_SPACE cRepeat:1 ScanCode:39 fExtended:0 fAltDown:0 fRepeat:0 fUp:0 [wParam:00000020 lParam:00390001 time:1:07:38.116 point:(183, 290)]
<00002> 00090902 P WM_CHAR chCharCode:'32' (32) cRepeat:1 ScanCode:39 fExtended:0 fAltDown:0 fRepeat:0 fUp:0 [wParam:00000020 lParam:00390001 time:1:07:38.116 point:(183, 290)]
<00003> 00090902 P WM_KEYUP nVirtKey:VK_SPACE cRepeat:1 ScanCode:39 fExtended:0 fAltDown:0 fRepeat:1 fUp:1 [wParam:00000020 lParam:C0390001 time:1:07:38.163 point:(183, 290)]

Spy ++是随Visual Studio提供的工具。您可以在C:\Program Files\Microsoft Visual Studio XYZ\Common7\Tools\spyxx.exe找到它,其中XYZ是8,9.0和10.0,我可以确认。

你可以做些什么来进一步测试时间,让Spy ++监听键盘命令和WM_PAINT等,看看程序响应键盘消息的UI变化有多快。

例如,以下是已经使用3+3计算器后的干净日志,然后按Enter。您可以看到计算器能够在KeyDown和KeyUp之间的.062ms之前进行计算和显示。

<00001> 00090902 P WM_KEYDOWN nVirtKey:VK_RETURN cRepeat:1 ScanCode:1C fExtended:1 fAltDown:0 fRepeat:0 fUp:0 [wParam:0000000D lParam:011C0001 time:1:19:12.539 point:(179, 283)]
<00002> 00090902 S WM_PAINT hdc:00000000 [wParam:00000000 lParam:00000000]
<00003> 00090902 R WM_PAINT lResult:00000000
<00004> 00090902 S WM_PAINT hdc:00000000 [wParam:00000000 lParam:00000000]
<00005> 00090902 R WM_PAINT lResult:00000000
<00006> 00090902 S WM_PAINT hdc:00000000 [wParam:00000000 lParam:00000000]
<00007> 00090902 R WM_PAINT lResult:00000000
<00008> 00090902 S WM_PAINT hdc:00000000 [wParam:00000000 lParam:00000000]
<00009> 00090902 R WM_PAINT lResult:00000000
<00010> 00090902 P WM_KEYUP nVirtKey:VK_RETURN cRepeat:1 ScanCode:1C fExtended:1 fAltDown:0 fRepeat:1 fUp:1 [wParam:0000000D lParam:C11C0001 time:1:19:12.601 point:(179, 283)]

编辑 - 在Spy ++中我建议使用Logging Options,它会显示Message Options对话框。转到消息选项卡,单击全部清除,选中'键盘',然后滚动列表框并选择WM_PAINT。这样您就只有所需的消息,否则您将被它们淹没。