(CORE) CORE (2022)

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.



Comments ( )
Link to this page: http://www.vb-net.com/WindowsTimer/Index.htm
< THANKS ME>