【问题标题】:WPF keyDown response time accuracyWPF keyDown 响应时间准确度
【发布时间】:2018-03-06 18:25:14
【问题描述】:

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

我只写了几行代码来测试默认设置:

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?

【问题讨论】:

  • 不知道你想在那里测量什么,也不知道你是怎么做的。 KeyDown 在按键按下时不会连续升起(我猜是KeyPress),所以按住空格键不应该多次更新标签,是吗?如果您的意思是在显示窗口之前按住空格键,那么您可以在构造函数之间对代码进行基准测试,直到窗口最终处于活动状态并泵送事件。
  • 据我所知,调度事件的速度尽可能快,并且与任何其他 Windows 应用程序的工作方式几乎相同:按下的键由操作系统处理,然后调度一个消息到您的窗口消息队列,然后将其泵入并在焦点控件上引发相应的事件。与人类的反应时间相比,准确性已经足够好,除非您在 UI 线程上执行工作
  • 按键的速度将取决于 Windows 配置的按键重复率。 windows.microsoft.com/is-is/windows-xp/help/…
  • @Tony:那不是KeyPress吗?
  • 不同的键盘/硬件也会有不同的延迟率。例如,无绳键盘的每次按键延迟都比 USB 高。然后是笔记本电脑键盘(集成)、PS/2、蓝牙、适配器延迟(例如 PS/2 到 USB)等。所以,你的问题是关于准确性,但是,我不确定软件应用程序可以做到这一点“准确”。除了上面的按键注释之外,还有您正在与之竞争的 windows 值:superuser.com/questions/388160/…

标签: c# .net wpf


【解决方案1】:

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

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

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

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


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

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

【讨论】:

    【解决方案2】:

    首先,如果Stopwatch.IsHighResolutiontrue,那么Stopwatch使用QueryPerformanceCounter,它可以以

    其次,当您按住空格键时,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 上更有效地渲染。)

    【讨论】:

    • 我可能会建议更改用户注册表可能不是最好的方法,但只需读取该值并检测指定键的持有情况。
    • @SeToY 我更改了注册表只是为了测试我对连续按下空格时 OP 测量的 33 毫秒间隔的假设。时间间隔测量的精度与此无关,因此OP不应修改用户的注册表。
    【解决方案3】:

    我想在这里指出另一个工具来跟踪您的时间安排。由于您正在考虑测试应用程序的响应,并且正如有人提到这涉及操作系统的消息,您可以利用 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 的计算器,然后按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)]
    

    edit- 在 Spy++ 中,我建议转到显示消息选项对话框的日志选项。转到消息选项卡,单击全部清除,选中“键盘”,然后滚动列表框并选择 WM_PAINT。这样你就只有想要的消息了,否则你会被它们淹没。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-22
      • 1970-01-01
      • 1970-01-01
      • 2021-11-03
      • 1970-01-01
      • 2020-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多