关于.net:从C#中的计时器获取准确的滴答

关于.net:从C#中的计时器获取准确的滴答

Getting accurate ticks from a timer in C#

我正在尝试重建原来使用C ++中的MFC编写的旧节拍器应用程序,然后使用C#在.NET中编写。我遇到的问题之一是使计时器足够准确地"打勾"。

例如,假设简单的BPM(每分钟心跳数)为120,则计时器应每0.5秒(或500毫秒)滴答一次。但是,将其用作刻度的基础并不完全准确,因为.NET仅保证您的计时器不会在经过的时间之前滴答。

目前,为了解决上面使用的相同的120 BPM示例,我将刻度设置为大约100毫秒,并且仅在每5个计时器刻度上播放一次咔嗒声。确实确实提高了准确性,但是感觉有点像骇客。

那么,获得准确刻度的最佳方法是什么?我知道可以使用的计时器比Visual Studio中可以使用的Windows窗体计时器要多,但是我对它们并不真正熟悉。


.NET中有三个计时器类,称为"计时器"。听起来您正在使用Windows Forms,但实际上您可能会发现System.Threading.Timer类更有用-但要小心,因为它会调用池线程,因此您不能直接从回调。

另一种方法可能是p /调用Win32多媒体计时器-timeGetTime,timeSetPeriod等。

一个快速的Google发现了这一点,这可能很有用http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx

"多媒体"(计时器)是在这种情况下要搜索的流行语。


另一种可能是DispatcherTimer的WPF实现中存在错误
(毫秒数和滴答数之间不匹配,这取决于准确的流程执行时间,可能导致不准确),如下所示:

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/DispatcherTimer.cs,143

1
2
3
4
5
6
7
8
9
10
11
12
13
class DispatcherTimer
{
    public TimeSpan Interval
    {
        set
        {
            ...
            _interval = value;
            // Notice below bug: ticks1 + milliseconds [Bug1]
            _dueTimeInTicks = Environment.TickCount + (int)_interval.TotalMilliseconds;
        }
    }
}

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dispatcher
{
    private object UpdateWin32TimerFromDispatcherThread(object unused)
    {
        ...
        _dueTimeInTicks = timer._dueTimeInTicks;
        SetWin32Timer(_dueTimeInTicks);
    }

    private void SetWin32Timer(int dueTimeInTicks)
    {
        ...
        // Notice below bug: (ticks1 + milliseconds) - ticks2  [Bug2 - almost cancels Bug1, delta is mostly milliseconds not ticks]
        int delta = dueTimeInTicks - Environment.TickCount;
        SafeNativeMethods.SetTimer(
            new HandleRef(this, _window.Value.Handle),
            TIMERID_TIMERS,
            delta); // <-- [Bug3 - if delta is ticks, it should be divided by TimeSpan.TicksPerMillisecond = 10000]
    }
}

http://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/SafeNativeMethodsCLR.cs,505

1
2
3
4
5
6
class SafeNativeMethodsPrivate
{
    ...
    [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
    public static extern IntPtr SetTimer(HandleRef hWnd, int nIDEvent, int uElapse, NativeMethods.TimerProc lpTimerFunc);
}

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906%28v=vs.85%29.aspx

1
2
3
uElapse [in]
Type: UINT
The time-out value, in milliseconds. // <-- milliseconds were needed eventually


开发最近的数据记录项目时遇到了这个问题。 .NET计时器(windows.forms,system.threading和system.timer)存在的问题是,它们只能精确到10毫秒左右,这是由于我相信.NET中内置的事件计划。 (我在这里谈论的是.NET 2)。这对我来说是不可接受的,因此我不得不使用多媒体计时器(您需要导入dll)。我还为所有计时器编写了包装器类,因此您可以根据需要使用最少的代码更改在它们之间进行切换。在这里查看我的博客文章:
http://www.indigo79.net/archives/27


C ++应用程序使用什么?您可以始终使用相同的东西,也可以将计时器代码从C ++包装到C ++ / CLI类中。


System.Windows.Forms.Timer的精度限制为55毫秒...


当计时器"滴答"事件代码在下一个"滴答"发生时尚未完成执行时,计时器类可能会开始表现异常。解决此问题的一种方法是在滴答事件开始时禁用计时器,然后在结束时重新启用计时器。

但是,此方法不适用于"滴答"代码的执行时间在滴答计时中不可接受的错误的情况下,因为计时器将在这段时间内被禁用(不计数)。

如果禁用计时器是一种选择,那么您还可以通过创建一个单独的线程来执行,睡眠x毫秒,执行,睡眠等操作,从而达到相同的效果。


推荐阅读