WPF按键响应时间精度

9

我正在开发一个应用程序,用户可以看到某些东西,并通过按键盘上的键来做出反应。反应时间非常关键,越准确越好。

我编写了一个示例应用程序,仅使用几行代码在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界面?


按键的速度取决于Windows的按键重复率配置。http://windows.microsoft.com/is-is/windows-xp/help/adjust-the-character-repeat-rate - Tony
@Tony:那不是应该用于“KeyPress”吗? - jods
1
不同的键盘/硬件也会有不同的延迟率。例如,无线键盘每次按键的延迟比USB高。然后还有笔记本电脑键盘(集成的)、PS/2、蓝牙、适配器延迟(例如PS/2到USB),等等。所以,你的问题是关于准确性的,但是,我不确定软件应用程序能够“准确地”做到这一点。除了上面的按键评论之外,这里是你要竞争的Windows值:http://superuser.com/questions/388160/keyboard-repeat-rate-repeat-delay-values-in-win7 - John S.
此外,我认为测量物理按键与主机应用程序处理之间的超精确时间的唯一方法是使用某种电子定制硬件连接到按键和软件输出。 - Tony
只有按键按下,示例可能不是最好的,但它表明即使每次按住按键,我也会得到不同的结果,而我不知道为什么,如果WPF响应精度为1毫秒,那么每次结果都应该相同,嗯? - gruber
显示剩余6条评论
3个回答

12

您的测试肯定是无效的,正如其他几位贡献者指出的那样,它只是测量了键盘重复率。但它有一个意外的好处,您可以看到您不会遇到键盘问题。

Windows 中的大多数事件发生的速率由时钟中断率确定。默认情况下,时钟每秒会跳动 64 次,即每 15.625 毫秒一次。这会唤醒内核并查看是否需要执行某些操作,寻找要传递给处理器核心的任务。通常情况下没有任务可执行,核心会通过 HLT 指令关闭,直到下一次中断发生。

因此,你的测试精度最高只可能达到 15.625 毫秒。您的观察恰好与这个数字的两倍相符,但实际情况并非如此,您可以使用程序来验证。使用控制面板 + 键盘,并调整“重复频率”滑块。注意,您可以调整它并使您的数字更改为不是 15.625 的倍数的值。

这不完全是偶然的,键盘控制器也会生成中断,就像时钟一样。您已经证明该中断本身已足够好,可以让您的程序重新激活。而且,您可以确定键盘控制器本身足够快地扫描键盘矩阵。您从键盘上得到的误差范围不会超过 +/- 2 毫秒,大约是您看到的显示数字中的噪声。如果您有一个扫描速度较慢的键盘,您可以使用此测试将其排除。


您更大的担忧是视频。视频适配器通常以每秒 60 次的频率刷新液晶监视器。因此,在最坏情况下,测试对象在 17 毫秒内无法“看到”该图像。而且液晶监视器本身也不那么快,便宜的液晶屏响应时间为 16 毫秒或更长,这是液体中的晶体无法快速翻转导致的副作用。

消除刷新率错误需要您的程序与垂直消隐期同步。通过DirectX可以做到这一点。你可以找到反应时间约为4毫秒的高清液晶显示器,这在游戏玩家中很受欢迎。


5
首先,如果 Stopwatch.IsHighResolutiontrue,那么 Stopwatch 使用 QueryPerformanceCounter,可以测量小于1毫秒的时间间隔。
其次,当你按住空格键时,Windows 会不断发送 WM_KEYDOWN 消息,你的秒表将测量这些消息之间的间隔。这个间隔由注册表键 HKCU\Control Panel\Keyboard\KeyboardSpeed 决定。
它的默认值是31,这是最快的重复率,意味着大约每秒30个字符。这就是为什么你测量到大约1000/30=33毫秒的间隔。
如果将其设置为0,即最慢的重复率,意味着大约每秒2个字符,那么你应该测量到约500毫秒的间隔。我已经使用这个设置测试了你的代码,并且确实得到了500毫秒的结果。(改变 KeyboardSpeed 后不要忘记重新启动 Windows!)
你需要捕获单个键按下事件,而不是重复的事件,这样你就不必更改KeyboardSpeed设置。你的程序应该向用户展示对象,启动秒表,并在键按下事件发生时停止它。ElapsedMilliseconds将给出反应时间。多次测量并使用平均值。
问题是,即使QueryPerformanceCounter准确地测量了经过的时间,键盘和Windows本身都会造成延迟,这会增加测量的反应时间。此外,延迟不是恒定的:如果Windows在应处理keydown事件的时候忙碌,那么延迟将会更大。因此,如果您认真对待这个任务,您应该校准您的程序。
我的意思是,你应该购买或建立一个基于微控制器的小型电子设备,它可以打开LED并检测打开LED与用户按下按钮之间的时间经过。使用这个设备进行10-20次(越多越好)反应时间测量,并与同一测试人员使用你的程序进行另外10-20次测量。两者之间的差异将给出键盘和Windows造成的延迟。这个差异可以从你的程序测量的反应时间中减去。
(有人可能会问,为什么你不应该使用小而精确的电子设备,而是使用Windows应用程序。首先,制造和销售软件比制造和销售硬件要容易和便宜得多。其次,可视化对象可以很复杂(例如棋盘),而复杂的对象可以在PC上更有效地呈现。)

我可能建议不要修改用户注册表来完成这个任务,而是直接读取该值并检测指定键的持有情况。 - SeToY
@SeToY 我改变了注册表,只是为了测试我的假设,即当连续按下空格键时 OP 测量到的 33 毫秒间隔。时间间隔测量的精度与此无关,因此 OP 不应修改用户的注册表。 - kol

1
我想指出另一个追踪时间的工具。由于您正在考虑测试应用程序的响应,并且正如有人提到的那样,这涉及到操作系统的消息,因此您可以利用Spy ++来查看这些消息的时间。我将按下空格键的输出复制到一个我仅监听键盘消息的窗口中,并打开了所有输出。我在一个通过对接站连接的USB键盘上尽可能快地按下并释放了一次空格键。您可以看到它需要约0.05毫秒来处理下降和上升。
<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更改的键盘消息的响应速度有多快。

例如,下面是在Calcuator中先输入3+3,然后按Enter后生成的干净日志。您可以看到,在KeyDown和KeyUp之间处理所花费的时间为0.062毫秒,而计算器在此之前已经计算并显示结果。

<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++中,我建议进入“日志选项”以查看“消息选项”对话框。转到“消息”选项卡,单击“清除所有”,勾选“键盘”,然后滚动列表框并选择WM_PAINT。这样您就只有所需的消息,否则您将被淹没在其中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接