我有一个应用程序,它使用System.Timers.Timer对象来引发由主窗体(Windows窗体,C#)处理的事件。我的问题是,无论我设置的.Interval有多短(甚至是1毫秒),我的最高值都是每秒64次。
我知道表单计时器有55 ms的精度限制,但这是System.Timer变体,而不是表单。
应用程序拥有1%的CPU,所以它绝对不是CPU绑定的.所以它所做的就是:
即使在没有其他工作可做的情况下,_Count也最多增加64次/秒。
这是一个“回放”应用程序,它必须复制数据包,在它们之间延迟只有1-2毫秒,所以我需要可靠地每秒触发1000次的东西(虽然如果我是CPU绑定的话,我会满足100次,但我没有)。
有什么想法吗?
发布于 2012-11-23 00:13:54
试试多媒体定时器 --它们为硬件平台提供了尽可能高的精度。这些计时器以比其他计时器服务更高的分辨率来调度事件。
您需要以下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);
和计时器功能结构
[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
}
发布于 2012-11-23 09:34:20
你可以坚持你的设计。您只需将系统中断频率设置为以其最大频率运行。为了获得这个结果,您只需在代码中的任何地方执行以下代码:
#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
的每个调用与对timeEndPeriod
__的调用相匹配,在两个调用中指定相同的最小分辨率。一个应用程序可以进行多个timeBeginPeriod
调用,只要每个调用与对timeEndPeriod
__的调用相匹配。
因此,所有定时器(包括您当前的设计)都将以更高的频率工作,因为定时器的粒度会提高。在大多数硬件上可以获得1ms的粒度。
下面列出了两个不同的硬件设置( wTimerRes
)的不同设置(A+B)所获得的中断周期:
可以很容易地看出,1ms是一个理论值。ActualResolution是以100 ns单位表示的。9,766表示0.9766毫秒,即每秒1024次中断。(实际上,它应该是0.9765625,即9,7656.25,100 ns单位,但这种精度显然不符合整数,因此由系统舍入。)
这一点也变得很明显。platform A并不真正支持timeGetDevCaps
返回的所有时间段(值介于wPeriodMin
和wPeriodMin
之间)。
摘要:多媒体计时器接口可以用来修改中断频率系统的宽度。因此,所有计时器都会改变它们的粒度。此外,系统时间更新将相应地改变,它将增加更多和更小的步骤。但是:实际行为取决于底层硬件。自从Windows 7和Windows 8引入了新的定时方案以来,这种硬件依赖性变得更小了。
发布于 2013-10-24 19:03:22
在其他解决方案和评论的基础上,我将这些VB.NET代码组合在一起。可以粘贴到带有表单的项目中。我理解了HansPassant的评论,他说只要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
<StructLayout(LayoutKind.Sequential)>
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
<DllImport("winmm.dll")>
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
<DllImport("winmm.dll")>
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
<DllImport("winmm.dll")>
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."
<DllImport("winmm.dll")>
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
<DllImport("winmm.dll")>
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
https://stackoverflow.com/questions/13521521
复制相似问题