9

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

我只写了几行代码来测试默认设置的示例应用程序 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?

4

3 回答 3

11

您的测试肯定是无效的,它只是测量其他几位贡献者指出的键盘重复率。但它对它意想不到的好处很有用,你实际上可以看到你不会对键盘有问题。

Windows 中的大多数事件发生的速率由时钟中断速率决定。默认情况下每秒滴答 64 次,每 15.625 毫秒一次。这会唤醒内核,它会查看是否需要做某事,寻找传递给处理器内核的工作。最典型的情况是无事可做,内核通过 HLT 指令关闭。直到下一个中​​断发生。

所以一个问题是你的测试永远不会比 15.625 毫秒更准确。你的观察结果偶然匹配,你看到的是这个数字的两倍。但实际情况并非如此,您可以使用您的程序来查看。使用控制面板 + 键盘并调整重复率滑块。请注意如何调整它并将您的数字更改为不是 15.625 的倍数的值。

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


你更关心的是视频。视频适配器通常以每秒 60 次更新刷新 LCD 监视器。所以最坏的情况是,测试对象在 17 毫秒内无法实际看到图像。而且液晶显示器本身也没有那么快,便宜的有16毫秒或更短的响应时间。液体中的水晶无法快速翻转的副作用。

消除刷新率错误需要您的程序与垂直消隐间隔同步。你可以用 DirectX 做一些事情。您可以找到响应时间约为 4 毫秒的高档液晶显示器,深受游戏玩家的欢迎。

于 2013-10-13T04:49:20.753 回答
5

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

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

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

如果将其设置为 0,即最慢的重复率,这意味着每秒大约 2 个字符,那么您应该测量大约 2 个字符。500 毫秒间隔。我已经用这个设置测试了你的代码,我确实得到了 500 毫秒。(更改 KeyboardSpeed 后不要忘记重新启动 Windows!)

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

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

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

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

于 2013-10-12T23:44:09.120 回答
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 更改。

例如,下面是3+3已经使用 Calculator 后的干净日志,然后按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。这样你就只有想要的消息,否则你会被它们淹没。

于 2013-10-16T13:18:48.190 回答