2

I was under the impression that System.Timers.Timer creates its own thread and that Microsoft recommends this type of timer to do tasks that do more accurate timing (as opposed to Windows.Forms.Timer, which runs in the UI thread).

The code below (I think) should be copy-and-pasteable into a project with an empty form. On my machine, I cannot get tmrWork to tick any faster than about 60 times per second, and it's amazingly unstable.

Public Class Form1

    Private lblRate As New Windows.Forms.Label
    Private WithEvents tmrUI As New Windows.Forms.Timer
    Private WithEvents tmrWork As New System.Timers.Timer

    Public Sub New()
        Me.Controls.Add(lblRate)

        InitializeComponent()
    End Sub

    Private StartTime As DateTime = DateTime.Now
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) _
        Handles MyBase.Load
        tmrUI.Interval = 100
        tmrUI.Enabled = True
        tmrWork.Interval = 1
        tmrWork.Enabled = True
    End Sub

    Private Counter As Integer = 0
    Private Sub tmrUI_Tick(sender As Object, e As System.EventArgs) _
        Handles tmrUI.Tick
        Dim Secs As Integer = (DateTime.Now - StartTime).TotalSeconds
        If Secs > 0 Then lblRate.Text = (Counter / Secs).ToString("#,##0.0")
    End Sub

    Private Sub tmrWork_Elapsed(sender As Object, e As System.Timers.ElapsedEventArgs) _
        Handles tmrWork.Elapsed
        Counter += 1
    End Sub
End Class

In this particular simple case, putting everything in tmrUI will yield the same performance. I guess I never tried to get a System.Timers.Timer to go too fast, but this performance looks just too bad to me. I wrote my own class to use the high performance timer in hardware but it seems like there should be a built-in timer that can do, say 100 ticks per second?

What's going on here?

darda
  • 3,597
  • 6
  • 36
  • 49
  • You need to be careful of thread safty, I don't think it can happen in this simple case but if a 2nd `tmrWork_Elapsed` started before the first finished both could be writing to `Counter` at the same time (With this short of a method it is extremely unlikely to happen, but when you write your "real" code it very easily could happen) – Scott Chamberlain Oct 24 '13 at 16:43
  • possible duplicate of [System.Timers.Timer only gives max 64 frames per second](http://stackoverflow.com/questions/13521521/system-timers-timer-only-gives-max-64-frames-per-second) – 500 - Internal Server Error Oct 24 '13 at 16:50
  • There's also `System.Threading.Timer`. You might want to look at this [MSDN article](http://msdn.microsoft.com/en-us/magazine/cc164015.aspx) for a comparison. – Brian Rogers Oct 24 '13 at 16:50
  • @ScottChamberlain - agreed; locking should be employed in such a case. – darda Oct 24 '13 at 17:20
  • @500-InternalServerError - I've let my program run for a while and indeed it stabilizes to 64/sec, as in the other [question](http://stackoverflow.com/questions/13521521/system-timers-timer-only-gives-max-64-frames-per-second) – darda Oct 24 '13 at 17:25
  • @BrianRogers - just tried `System.Threading.Timer` and all signs indicate it also saturates at 64 Hz. All the documentation I've seen, including the article you linked, imply that Microsoft never intended these timers to go fast. I think my test shows that if all three saturate at 64 Hz, then the underlying timing mechanism is identical; they are simply exposing different levels of thread safety to the same timer. – darda Oct 24 '13 at 17:33
  • 1
    @pelesl Yep, I tried your code, and I am seeing the same. Incidentally, using a Stopwatch instead of DateTime.Now to measure time, starting the stopwatch when the timers are enabled rather than when the form is instantiated, changing the increment to use Interlocked and doing the calculation with a double (rather than integer division) reduces the "instability" pretty significantly. It will converge on 64Hz very fast. – Brian Rogers Oct 24 '13 at 17:54
  • `System.Timers.Timer` is just a wrapper around `System.Threading.Timer`. It would be very surprising to see that one had better resolution than the other. See http://stackoverflow.com/q/3744032/56778 for information about why the timers have limited resolution. Also, please note that a timer does not "create its own thread." The only time another thread is involved is when the timer's elapsed event is being executed, and that's a threadpool thread that was created by the OS. A timer does not require a persistent thread. – Jim Mischel Oct 24 '13 at 19:22

2 Answers2

0

The above code for dbasnett throws an error at "Stop" below the Debug.WriteLine (during the debugging mode.) I recommend that contributors test their code before giving advice.

Stackoverflow is vital for finding the best coding solutions and it is up to us (the contributors) to do our best.

Each time an Elapsed is called, the callback is fired on its own thread. In addition, there is nothing stopping one Elapsed event handler from firing before the previous one is completed.

For further research, I highly recommend viewing the provided links below:

https://learn.microsoft.com/en-us/archive/msdn-magazine/2004/february/comparing-the-timer-classes-in-the-net-framework-class-library

https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=net-6.0

The following code is the correct solution and I personally tested it. Every 4 seconds, it will restart:

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        Clockwork.AutoReset = True
        Clockwork.Interval = 4000 '4 seconds
        AddHandler Clockwork.Elapsed, AddressOf Tick1
        Clockwork.Start()

    End Sub

    ReadOnly Clockwork As New Timers.Timer

    Private Sub Tick1(sender As Object, e As Timers.ElapsedEventArgs)

        Clockwork.Stop()
        MessageBox.Show("Timer's Working")
        Clockwork.Start() 'This will restart the timer

    End Sub
End Class
-1

To get near 100Hz try something like this which uses an AutoResetEvent.

Private Sub tmrWork()
    'start this as a background thread
    Dim tmr As New Threading.AutoResetEvent(False)
    Dim stpw As Stopwatch = Stopwatch.StartNew
    Const interval As Integer = 10 'the interval
    '
    '
    Do 'timer
        stpw.Stop()
        If stpw.ElapsedMilliseconds > interval Then
            tmr.WaitOne(interval) 'the interval
        Else
            tmr.WaitOne(CInt(interval - stpw.ElapsedMilliseconds)) 'the interval
        End If
        stpw.Restart()
        '
        'code to execute when 'timer' elapses
        '

    Loop
End Sub

Here is a test that shows that, depending on what you do in the loop, it is possible to fire off code at 100Hz.

Private Sub tmrWork()
    Dim tmr As New Threading.AutoResetEvent(False)
    Dim stpw As Stopwatch = Stopwatch.StartNew
    Const interval As Integer = 10 'the interval
    'for testing
    Dim cts As New List(Of Long)
    '
    '
    Do 'timer
        tmr.WaitOne(interval) 'wait for the interval
        cts.Add(stpw.ElapsedMilliseconds) 'add elapsed to list
        stpw.Restart() 'restart
        If cts.Count >= 500 Then 'five second test
            Debug.WriteLine(String.Format("Avg: {0},  Min: {1},  Max: {2}", cts.Average, cts.Min, cts.Max))
            Stop
        End If
    Loop
End Sub

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    Dim t As New Threading.Thread(AddressOf tmrWork)
    t.IsBackground = True
    t.Start()
End Sub

For Hans Pissant

Platform Timer Resolution:Platform Timer Resolution The default platform timer resolution is 15.6ms (15625000ns) and should be used whenever the system is idle. If the timer resolution is increased, processor power management technologies may not be effective. The timer resolution may be increased due to multimedia playback or graphical animations. Current Timer Resolution (100ns units) 100000 Maximum Timer Period (100ns units) 156001

Platform Timer Resolution:Outstanding Timer Request A program or service has requested a timer resolution smaller than the platform maximum timer resolution. Requested Period 100000 Requesting Process ID 452 Requesting Process Path \Device\HarddiskVolume3\Windows\System32\svchost.exe

dbasnett
  • 11,334
  • 2
  • 25
  • 33
  • You need to get your machine fixed if this works. Run `powercfg.exe /energy` to get a diagnostic, pay attention to notes about "Platform Timer Resolution" to find out what process is messing with the clock interrupt rate. – Hans Passant Oct 24 '13 at 18:08
  • Interesting solution. My High Performance Timer class is similar, that is, monitor the elapsed time in a loop and fire off events/callbacks as necessary. – darda Oct 24 '13 at 19:14
  • @HansPassant - The minimum I would expect to see in any circumstance, except maybe a loop is 10 ms. – dbasnett Oct 24 '13 at 20:20
  • The powercfg report tells you the minimum you should expect, did you look? The point is that it won't be the minimum on another machine. Your code only runs correctly on your machine. – Hans Passant Oct 24 '13 at 20:24
  • @HansPassant And yours? Did you run it? As far as I know I haven't made any changes. I have a plain old Dell Laptop. – dbasnett Oct 24 '13 at 20:28
  • Thanks for the down vote. The only 'advice' you gave was to run something, which I did. Before your down vote you could have provided some insight into what to look for since you have remotely diagnosed my PC as defective. Thanks! Sorry to offend one of the SOG's. – dbasnett Oct 24 '13 at 21:13
  • @HansPassant - apparently more than a few of us have to get our PC's fixed. If you care see... http://www.vbforums.com/showthread.php?739901-AutoResetEvent-WaitOne-Intervals&p=4537223&posted=1#post4537223 , a much more civil forum. – dbasnett Oct 24 '13 at 21:23