System.Timers.Timer仅提供每秒最多64帧

我有一个应用程序,它使用System.Timers.Timer对象来引发由主窗体( Windows窗体 ,C#)处理的事件。 我的问题是,无论我设置.Interval(甚至1毫秒)有多短,我每秒最多得64次。

我知道Forms计时器的精度限制为55毫秒,但这是System.Timer变体,而不是Forms表单。

该应用程序占用1%的CPU,因此它肯定不受CPU限制。 所以它所做的只是:

  • 将Timer设置为1&nsp; ms
  • 触发事件时,增加_Count变量
  • 再次将其设置为1&nsp; ms并重复

即使没有其他工作要做,_Count每秒最多增加64次。

这是一个“回放”应用程序,它必须复制进来的数据包,它们之间只有1-2毫秒的延迟,所以我需要能够每秒可靠地发射1000次的东西(尽管如果我能满足100的话)是CPU限制,我不是)。

有什么想法吗?

尝试多媒体计时器 – 它们为硬件平台提供了最高的准确性。 这些计时器以比其他计时器服务更高的分辨率安排事件。

您将需要以下Win API函数来设置计时器分辨率,启动和停止计时器:

 [DllImport("winmm.dll")] private static extern int timeGetDevCaps(ref TimerCaps caps, int sizeOfTimerCaps); [DllImport("winmm.dll")] private static extern int timeSetEvent(int delay, int resolution, TimeProc proc, int user, int mode); [DllImport("winmm.dll")] private static extern int timeKillEvent(int id); 

您还需要回调委托:

 delegate void TimeProc(int id, int msg, int user, int param1, int param2); 

和计时器function结构

 [StructLayout(LayoutKind.Sequential)] public struct TimerCaps { public int periodMin; public int periodMax; } 

用法:

 TimerCaps caps = new TimerCaps(); // provides min and max period timeGetDevCaps(ref caps, Marshal.SizeOf(caps)); int period = 1; int resolution = 1; int mode = 0; // 0 for periodic, 1 for single event timeSetEvent(period, resolution, new TimeProc(TimerCallback), 0, mode); 

回调:

 void TimerCallback(int id, int msg, int user, int param1, int param2) { // occurs every 1 ms } 

你可以坚持你的设计。 您只需将系统中断频率设置为以最大频率运行。 为了获得这一点,您只需在代码中的任何位置执行以下代码:

 #define TARGET_RESOLUTION 1 // 1-millisecond target resolution TIMECAPS tc; UINT wTimerRes; if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) { // Error; application can't continue. } wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax); timeBeginPeriod(wTimerRes); 

这将强制系统中断周期以最大频率运行。 它是一种系统范围的行为,因此甚至可以在单独的过程中完成。 别忘了使用

 MMRESULT timeEndPeriod(wTimerRes ); 

完成后释放资源并将中断周期重置为默认值。 有关详细信息,请参阅多媒体计

您必须timeBeginPeriod每次调用与对timeBeginPeriod的调用相timeEndPeriod ,并在两次调用中指定相同的最小分辨率。 只要每次调用与对timeEndPeriod的调用匹配,应用程序就可以进行多次timeBeginPeriod调用。

因此,所有定时器(包括您当前的设计)将以更高的频率运行,因为定时器的粒度将得到改善。 在大多数硬件上可以获得1 ms的粒度。

以下是针对两种不同硬件设置(A + B)的wTimerRes各种设置获得的中断周期列表:

ActualResolution(中断周期)与wTimerRes的设置

可以容易地看出,1ms是理论值。 ActualResolution以100 ns为单位给出。 9,766表示0.9766 ms,即每秒1024个中断。 (实际上它应该是0.9765625,这将是9,7656.25 100 ns单位,但是这个精度显然不适合整数,因此被系统舍入。)

很明显,ig平台A并不真正支持timeGetDevCaps返回的所有周期范围(值在wPeriodMinwPeriodMin之间)。

摘要:多媒体定时器接口可用于修改系统中断频率。 因此,所有计时器都会改变其粒度。 此外,系统时间更新将相应地更改,它将以更小的步骤更频繁地增加。 但是:实际行为取决于底层硬件。 自从引入了更新的计时方案以来,自Windows 7和Windows 8引入以来,这种硬件依赖性已经变得更小了。

基于其他解决方案和注释,我将这个VB.NET代码放在一起。 可以粘贴到带有表单的项目中。 我理解@ HansPassant的评论说,只要调用timeBeginPeriod ,“常规定时器也会变得准确”。 在我的代码中似乎不是这种情况。

在使用timeBeginPeriod将计时器分辨率设置为最小值后,我的代码创建了一个多媒体计时器,一个System.Threading.Timer ,一个System.Timers.Timer和一个Windows.Forms.Timer 。 多媒体定时器根据需要以1 kHz运行,但其他定时器仍然停留在64 Hz。 所以我要么做错了,要么就是无法改变内置.NET计时器的分辨率。

编辑 ; 更改了代码以使用StopWatch类进行计时。

 Imports System.Runtime.InteropServices Public Class Form1 'From http://www.pinvoke.net/default.aspx/winmm/MMRESULT.html Private Enum MMRESULT MMSYSERR_NOERROR = 0 MMSYSERR_ERROR = 1 MMSYSERR_BADDEVICEID = 2 MMSYSERR_NOTENABLED = 3 MMSYSERR_ALLOCATED = 4 MMSYSERR_INVALHANDLE = 5 MMSYSERR_NODRIVER = 6 MMSYSERR_NOMEM = 7 MMSYSERR_NOTSUPPORTED = 8 MMSYSERR_BADERRNUM = 9 MMSYSERR_INVALFLAG = 10 MMSYSERR_INVALPARAM = 11 MMSYSERR_HANDLEBUSY = 12 MMSYSERR_INVALIDALIAS = 13 MMSYSERR_BADDB = 14 MMSYSERR_KEYNOTFOUND = 15 MMSYSERR_READERROR = 16 MMSYSERR_WRITEERROR = 17 MMSYSERR_DELETEERROR = 18 MMSYSERR_VALNOTFOUND = 19 MMSYSERR_NODRIVERCB = 20 WAVERR_BADFORMAT = 32 WAVERR_STILLPLAYING = 33 WAVERR_UNPREPARED = 34 End Enum 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757625(v=vs.85).aspx  Public Structure TIMECAPS Public periodMin As UInteger Public periodMax As UInteger End Structure 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757627(v=vs.85).aspx  Private Shared Function timeGetDevCaps(ByRef ptc As TIMECAPS, ByVal cbtc As UInteger) As MMRESULT End Function 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx  Private Shared Function timeBeginPeriod(ByVal uPeriod As UInteger) As MMRESULT End Function 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757626(v=vs.85).aspx  Private Shared Function timeEndPeriod(ByVal uPeriod As UInteger) As MMRESULT End Function 'http://msdn.microsoft.com/en-us/library/windows/desktop/ff728861(v=vs.85).aspx Private Delegate Sub TIMECALLBACK(ByVal uTimerID As UInteger, _ ByVal uMsg As UInteger, _ ByVal dwUser As IntPtr, _ ByVal dw1 As IntPtr, _ ByVal dw2 As IntPtr) 'Straight from C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include\MMSystem.h 'fuEvent below is a combination of these flags. Private Const TIME_ONESHOT As UInteger = 0 Private Const TIME_PERIODIC As UInteger = 1 Private Const TIME_CALLBACK_FUNCTION As UInteger = 0 Private Const TIME_CALLBACK_EVENT_SET As UInteger = &H10 Private Const TIME_CALLBACK_EVENT_PULSE As UInteger = &H20 Private Const TIME_KILL_SYNCHRONOUS As UInteger = &H100 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757634(v=vs.85).aspx 'Documentation is self-contradicting. The return value is Uinteger, I'm guessing. '"Returns an identifier for the timer event if successful or an error otherwise. 'This function returns NULL if it fails and the timer event was not created."  Private Shared Function timeSetEvent(ByVal uDelay As UInteger, _ ByVal uResolution As UInteger, _ ByVal TimeProc As TIMECALLBACK, _ ByVal dwUser As IntPtr, _ ByVal fuEvent As UInteger) As UInteger End Function 'http://msdn.microsoft.com/en-us/library/windows/desktop/dd757630(v=vs.85).aspx  Private Shared Function timeKillEvent(ByVal uTimerID As UInteger) As MMRESULT End Function Private lblRate As New Windows.Forms.Label Private WithEvents tmrUI As New Windows.Forms.Timer Private WithEvents tmrWorkThreading As New System.Threading.Timer(AddressOf TimerTick) Private WithEvents tmrWorkTimers As New System.Timers.Timer Private WithEvents tmrWorkForm As New Windows.Forms.Timer Public Sub New() lblRate.AutoSize = True Me.Controls.Add(lblRate) InitializeComponent() End Sub Private Capability As New TIMECAPS Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing timeKillEvent(dwUser) timeEndPeriod(Capability.periodMin) End Sub Private dwUser As UInteger = 0 Private Clock As New System.Diagnostics.Stopwatch Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) _ Handles MyBase.Load Dim Result As MMRESULT 'Get the min and max period Result = timeGetDevCaps(Capability, Marshal.SizeOf(Capability)) If Result <> MMRESULT.MMSYSERR_NOERROR Then MsgBox("timeGetDevCaps returned " + Result.ToString) Exit Sub End If 'Set to the minimum period. Result = timeBeginPeriod(Capability.periodMin) If Result <> MMRESULT.MMSYSERR_NOERROR Then MsgBox("timeBeginPeriod returned " + Result.ToString) Exit Sub End If Clock.Start() Dim uTimerID As UInteger uTimerID = timeSetEvent(Capability.periodMin, Capability.periodMin, _ New TIMECALLBACK(AddressOf MMCallBack), dwUser, _ TIME_PERIODIC Or TIME_CALLBACK_FUNCTION Or TIME_KILL_SYNCHRONOUS) If uTimerID = 0 Then MsgBox("timeSetEvent not successful.") Exit Sub End If tmrWorkThreading.Change(0, 1) tmrWorkTimers.Interval = 1 tmrWorkTimers.Enabled = True tmrWorkForm.Interval = 1 tmrWorkForm.Enabled = True tmrUI.Interval = 100 tmrUI.Enabled = True End Sub Private CounterThreading As Integer = 0 Private CounterTimers As Integer = 0 Private CounterForms As Integer = 0 Private CounterMM As Integer = 0 Private ReadOnly TimersLock As New Object Private Sub tmrWorkTimers_Elapsed(sender As Object, e As System.Timers.ElapsedEventArgs) _ Handles tmrWorkTimers.Elapsed SyncLock TimersLock CounterTimers += 1 End SyncLock End Sub Private ReadOnly ThreadingLock As New Object Private Sub TimerTick() SyncLock ThreadingLock CounterThreading += 1 End SyncLock End Sub Private ReadOnly MMLock As New Object Private Sub MMCallBack(ByVal uTimerID As UInteger, _ ByVal uMsg As UInteger, _ ByVal dwUser As IntPtr, _ ByVal dw1 As IntPtr, _ ByVal dw2 As IntPtr) SyncLock MMLock CounterMM += 1 End SyncLock End Sub Private ReadOnly FormLock As New Object Private Sub tmrWorkForm_Tick(sender As Object, e As System.EventArgs) Handles tmrWorkForm.Tick SyncLock FormLock CounterForms += 1 End SyncLock End Sub Private Sub tmrUI_Tick(sender As Object, e As System.EventArgs) _ Handles tmrUI.Tick Dim Secs As Integer = Clock.Elapsed.TotalSeconds If Secs > 0 Then Dim TheText As String = "" TheText += "System.Threading.Timer " + (CounterThreading / Secs).ToString("#,##0.0") + "Hz" + vbCrLf TheText += "System.Timers.Timer " + (CounterTimers / Secs).ToString("#,##0.0") + "Hz" + vbCrLf TheText += "Windows.Forms.Timer " + (CounterForms / Secs).ToString("#,##0.0") + "Hz" + vbCrLf TheText += "Multimedia Timer " + (CounterMM / Secs).ToString("#,##0.0") + "Hz" lblRate.Text = TheText End If End Sub End Class