17

我正在制作一个需要非常紧迫的时间的应用程序,而 Stopwatch 类是完美的解决方案。但是,我有时注意到,在小型平板电脑上运行时,秒表值会偏离。我添加了一些调试打印输出,每 200 毫秒左右监控一次秒表的值:

0:00:197
0:00:502
0:00:702
...
0:03:356
0:12:93

0:13:21
0:13:421
...

它怎么可能从约 3 秒跳到约 13 秒?我现在看到底层函数 QueryPerformanceCounter() 是错误的(当心 QueryPerformanceCounter()),但我感觉到这里正在发生其他事情。

任何见解都值得赞赏。

更新:

这是我的代码的更多细节:它非常简单。这是一个 WPF 应用程序,它在启动时创建一个新Stopwatch对象,然后通过Start(). 然后我创建一个DispatcherTimer,像这样:

displayTimer = new DispatcherTimer(); 
displayTimer.Tick += display_Tick; 
displayTimer.Interval = DISPLAY_INTERVAL_TIMESPAN; 

其中时间跨度为 200 ms。Stopwatch我的调试代码只是在每次dispatchTimer滴答声时打印出对象的值。

更新2:

一篇有趣的 Microsoft 支持文章是性能计数器值可能会意外飞跃

4

3 回答 3

17

更新(查看您的日志后)

正如您已经提到的,Stopwatch该类使用QueryPerformanceCounter下面的函数。在备注部分 MSDN 说:

在多处理器计算机上,调用哪个处理器并不重要。但是,由于基本输入/输出系统 (BIOS) 或硬件抽象层 (HAL) 中的错误,您可能会在不同的处理器上获得不同的结果。要指定线程的处理器亲和性,请使用 SetThreadAffinityMask 函数。

当您使用 Dispatcher时,QueryPerformanceCounter每次查询经过的时间时,可能不会在同一个 CPU 上执行。

您可以通过为您的进程指定处理器亲和性来检查 MSDN 中提到的问题是否是您的问题的原因,例如通过使用start命令调用您的可执行文件。对我来说,CPU 之间的 10 秒似乎是一个很大的延迟,但是文档对于差异可能有多大非常模糊。以下命令会将您的应用程序绑定到第一个 CPU:

> start.exe /AFFINITY 1 program.exe

如果这应该解决问题,您可能需要查看建议的解决方法,即SetThreadAffinityMask在查询Stopwatch对象之前调用该函数。

您的评论说您正在使用 WPF DispatcherTimer。该类的文档指出:

定时器不能保证在时间间隔发生时准确执行,但可以保证在时间间隔发生之前不会执行。这是因为 DispatcherTimer 操作像其他操作一样放置在 Dispatcher 队列中。DispatcherTimer 操作何时执行取决于队列中的其他作业及其优先级。

这意味着计时器事件可能会延迟到达,尤其是在调度程序忙于其他任务时。您是否在调度程序队列中放置了其他会阻止事件更早触发的东西?

于 2010-08-03T20:23:29.650 回答
15

PC 硬件上的确切时间并不简单。这里有一些资源,展示了一些在 Windows 中计时的经验,这将是 Windows 环境中任何计数器的底层实现,包括 .NET:

它解释了为什么有时您会获得 10 毫秒的分辨率,具体取决于您使用的系统调用,并更清楚地说明了 QueryPerformanceCounter 为何“有问题”。提出的要点之一是省电模式/可变 CPU 速度会干扰这些时序。

与此相关的一个概念是实时物理模拟中的“锁定时间步长”。如果你用谷歌搜索,你可能会得到一些关于如何解决你遇到的问题的想法。基本概念是您有固定的时间步长,并为您的计时/更新功能执行一种生产者/消费者实现。我不知道这是否适用于您的特定领域,但它被认为是视频游戏中的最佳实践。

于 2010-08-03T19:57:50.387 回答
2

也许我误解了你的问题,但是你需要只测量时间间隔还是需要在这些时间间隔执行代码?如果您需要执行代码,请查看 System.Timers.Timer 类,因为它是线程安全的,并且应该适用于多处理器。

于 2010-08-03T21:11:27.643 回答