Windows timers investigation (API Timer, Stopwatch Timer, Multimedia Timer).
Datatime.Now() always sore strong date, but Windows is not a real-time OS, therefore Timer.Tick (in Windows.Form) always raise not in exactly time we expected.
There are three more accurate timers in Windows.
API Timer
First timer based on Windows API. This is codebase of this timer.
1: Imports Microsoft.Win32.SafeHandles
2: Imports System.ComponentModel
3: Imports System.Runtime.InteropServices
4: Imports System.Threading
5:
6: Public Class StrongTimer
7: Inherits WaitHandle
8:
9: <DllImport("kernel32.dll")>
10: Private Shared Function CreateWaitableTimer(ByVal lpTimerAttributes As IntPtr, ByVal bManualReset As Boolean, ByVal lpTimerName As String) As SafeWaitHandle
11:
12: End Function
13:
14: <DllImport("kernel32.dll", SetLastError:=True)>
15: Private Shared Function SetWaitableTimer(ByVal hTimer As SafeWaitHandle,
16: <[In]> ByRef pDueTime As Long, ByVal lPeriod As Integer, ByVal pfnCompletionRoutine As IntPtr, ByVal lpArgToCompletionRoutine As IntPtr,
17: <MarshalAs(UnmanagedType.Bool)> ByVal fResume As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
18:
19: End Function
20:
21: Public Sub New(ByVal Optional manualReset As Boolean = True, ByVal Optional timerName As String = Nothing)
22: Me.SafeWaitHandle = CreateWaitableTimer(IntPtr.Zero, manualReset, timerName)
23: End Sub
24:
25: Public Sub [Set](ByVal dueTime As Long)
26: If Not SetWaitableTimer(Me.SafeWaitHandle, dueTime, 0, IntPtr.Zero, IntPtr.Zero, False) Then
27: Throw New Win32Exception()
28: End If
29: End Sub
30:
31: End Class
Code for perform this class and measure time intervar can look as:
1: Dim MainTimer = New StrongTimer(True, "MainTimer")
2: Dim CurTime As DateTime = Now
3: Dim NextTick = CurTime.AddMilliseconds(100000)
4: Dim NextTickTime = NextTick.ToFileTime
5: MainTimer.Set(NextTickTime)
6: For i = 0 To 1000
7: CurTime = Now
8: MainTimer.WaitOne(100)
9: Console.WriteLine(String.Format("{0:N4}", New TimeSpan(Now.Ticks - CurTime.Ticks).Milliseconds))
10: Next
11: Console.ReadLine()
And this is result of my investigation of this timer - first screen in empty loading computer, ans second screen in high loading computer.
Stopwatch Timer.
This is second timer, usually most accuracy and this is full managed code. This is simplest way to understand how it working.
1: Dim StopWatchTimer As New Stopwatch
2: StopWatchTimer.Start()
3: For i = 0 To 1000
4: Dim StartTick As Long = StopWatchTimer.ElapsedTicks
5: System.Threading.Thread.Sleep(100)
6: Dim EndtTick As Long = StopWatchTimer.ElapsedTicks
7: Console.WriteLine(String.Format("{0:N4}", New TimeSpan(EndtTick - StartTick).Milliseconds))
8: Next
9: StopWatchTimer.Stop()
10: Console.ReadLine()
This is more realistic solution with events.
100:
101: ThreadDelegate = New Threading.ThreadStart(Sub() StopWatchTimer.DoWork(AddressOf StopWatch_Tick))
102: StopwatchThread = New Threading.Thread(ThreadDelegate)
103: StopwatchThread.Name = "StopwatchThread"
104: StopwatchThread.Priority = Threading.ThreadPriority.Highest
105: StopwatchThread.Start()
106: End Sub
107:
108: Public Shared StopwatchThread As Threading.Thread
109: Public Shared ThreadDelegate As Threading.ThreadStart
110: Public Shared PrevMilliseconds As Long
111:
112:
113: Shared Sub StopWatch_Tick(ElapsedMilliseconds As Long)
114: Debug.WriteLine(ElapsedMilliseconds - PrevMilliseconds)
115: PrevMilliseconds = ElapsedMilliseconds
116: End Sub
And this is result of my investigation, first screen without another program on the same computer and second screen on high loading computer.
Multimedia Timer.
This is third independent solution - from Winamp.
1: Imports System.Runtime.InteropServices
2:
3: Public Class AccurateTimer
4:
5: Private Delegate Sub TimerEventDel(ByVal id As Integer, ByVal msg As Integer, ByVal user As IntPtr, ByVal dw1 As Integer, ByVal dw2 As Integer)
6: Private Const TIME_PERIODIC As Integer = 1
7: Private Const EVENT_TYPE As Integer = TIME_PERIODIC
8:
9: <DllImport("winmm.dll")>
10: Private Shared Function timeBeginPeriod(ByVal msec As Integer) As Integer
11:
12: End Function
13: <DllImport("winmm.dll")>
14: Private Shared Function timeEndPeriod(ByVal msec As Integer) As Integer
15:
16: End Function
17: <DllImport("winmm.dll")>
18: Private Shared Function timeSetEvent(ByVal delay As Integer, ByVal resolution As Integer, ByVal handler As TimerEventDel, ByVal user As IntPtr, ByVal eventType As Integer) As Integer
19:
20: End Function
21: <DllImport("winmm.dll")>
22: Private Shared Function timeKillEvent(ByVal id As Integer) As Integer
23:
24: End Function
25:
26: Private mAction As Action
27: Private mTimerId As Integer
28: Private mHandler As TimerEventDel
29:
30: Public Sub New(ByVal action As Action, ByVal delay As Integer)
31: mAction = action
32: timeBeginPeriod(1)
33: mHandler = New TimerEventDel(AddressOf TimerCallback)
34: mTimerId = timeSetEvent(delay, 0, mHandler, IntPtr.Zero, EVENT_TYPE)
35: End Sub
36:
37: Public Sub [Stop]()
38: Dim err As Integer = timeKillEvent(mTimerId)
39: timeEndPeriod(1)
40: System.Threading.Thread.Sleep(100)
41: End Sub
42:
43: Dim I As Integer = 0
44: Private Sub TimerCallback(ByVal id As Integer, ByVal msg As Integer, ByVal user As IntPtr, ByVal dw1 As Integer, ByVal dw2 As Integer)
45: I = I + 1
46: If I = 1000 Then [Stop]()
47: mAction.Invoke
48: End Sub
49: End Class
And this is simple way of measuring this timer.
1: MMStart = Now
2: Dim MultimediaTimer = New AccurateTimer(AddressOf WriteTime, 100) ' In milliseconds. 10 = 1/100th second
3: Console.ReadLine()
4: MultimediaTimer.Stop()
5: End Sub
6:
7: Sub WriteTime()
8: Console.WriteLine(String.Format("{0:N4}", New TimeSpan(Now.Ticks - MMStart.Ticks).Milliseconds))
9: MMStart = Now
10: End Sub
This is result of my investigation, first screen in empty computer and second screen in high loading computer.
So, Windows is not a real-time system, any timer don't working fine in high loading computer. In empty computer Stopwatch timer maybe a best, but Multimedia timer is also not so bad.
|