0

-------- start update and conclusion (30.08.2023) --------------
My timer code (postend below) run's without any problem in a windows forms client (vb.net). Starts to the "full minute" and then is fires any 10 seconds (the whole day).
The same code don't work in a windows service: don't fire any 10 seconds and delays a few seconds over a full day.
So... there are definitely differences, how a timer is handled in a windows client and in a windows service (I still don't know the reason).
I now have implemented the code from @dbasenett (see his answer to my question) in my windows service an let it run over the last night.
Result:
enter image description here

The timer starts to the full minute and then fires any 10 seconds over a full day (night).
NDD_Zeit ( 1 ) is the timestamp, when the data were stored on the SQL server and NDD_TradeTime_US ( 2 ) is the real trade time to the data, delivered over the json API from my broker.
In common, the data are stored/from exact any 10 seconds ( 3 )
Rarely ( 4 ) there is 1 second gap. But if there is a gap, it is only for one tic (with the timer it has started e.g. to 13, then become 24 and so on -> over ten seconds in full day).
So.. many thanks to @dbasnett for your posting, your support and for your patience;-)
I will set your answer as solution.
@any that need an exact timer in a windows service and have a problem with it: take the last version of the code in the answer of @dbasnett.

----------------------- end update und conclusion ----------------

I’m on the way to develop a robo-trader (automatic trading over an API to a broker).
The robo-trader (windows-) client access the real time data over an API every 10 second (query the data, buy and sell).
To be able to do tests (change code in the windows robo-trader and see the results in a fast and easy way), I also have developed a windows service that get the data every 10 seconds (like the client) and then stores the data on a SQL server, so that I am able to do simulations with the data on the SQL server (with history data).
The base problem was (is), that the data are not in sync.
E.g. the windows client get’s the data at 10:02:13 whereby the windows service get’s the (same) data at 10:02:19 what returns other data and can make a big difference depending on the implemented logic.

Therefore I wanted to sync the timer tics to the same second.
To reach the target, I have implemented two timers: A one second timer, that starts the “real” 10 second timer (target: full minute). A ten second timer, that do the real job.

So... first the on second timer is started and waits for a time with 50 seconds and then start the ten second timer.
Means the 10 second timer has to start on hh:mm:00 (on a full minute) and then have to fire every 10 seconds.
In the windows client this work like a charm: The timer is started on a full minute and the fires any 10 second (E.G. 10:02:00, 10:02:10, 10:02:10 and so on) for the whole trading day.

In the windows service, this (same code as in client) does – for whatever reason – don’t work (what I don’t understand): The start of the ten second timer from the one second timer works correct (I can see this, as I write entries in the windows event log): The ten second timer is started correct at 50 seconds (e.g. at 10:01:50).
But, it then fires the first time e.g. at 10:02:03, 10:02:13 and so on and later “loose” further time and change e.g. to 11:02:04 and so on.
So.. it seems as the timer in the windows client don’t work the same as the timer in the windows service, what I don’t understand (reason for this post).
Notes:

  • The windows client and the windows service are running on the same machine (with the same clock, that is synced over the internet)
  • I also have tried to run the windows service on a server -> same result
  • The “work” in the ten second timer (query the data over the API and store the data on the SQL server) takes less than one second

So... the problem is, that the ten second timer in the windows service don’t fire after 10 seconds (he fires e.g. after 13 seconds the first time), after the (correct) start and later “lose” further time as longer as he is started, what I absolutely don’t understand (never seen such a behavior in a windows client).
I also have tried, to start the timer at 47 seconds (instead of 50 seconds) only to see, if it then starts on the full minute, what is not the case and confuse me in addition.

Code Snippets:

Timer definitions (in Class()

 Public TimerDatenAbruf As New System.Timers.Timer() ' 10 second timer that does the work
 Public TimerSekundenSync As New System.Timers.Timer() ' 1 Second timer that starts the 10 second timer

Timer setup (in OnStart()

 TimerDatenAbruf.Interval = 10 * 1000 ' 10 Seconds for the 10 second timer
 TimerSekundenSync.Interval = 1 * 1000 ' 1 Second for the 1 second timer
' Add of the handlers and start of the one second timer
 AddHandler TimerSekundenSync.Elapsed, AddressOf SekundenTimerEvent 
 TimerSekundenSync.Enabled = True
 TimerSekundenSync.Start()
 AddHandler TimerDatenAbruf.Elapsed, AddressOf TimerDatenAbrufTimerEvent

Code to one second timer in SekundenTimerEvent()

Private Sub SekundenTimerEvent(source As Object, e As ElapsedEventArgs)
' Sync to the minute
Dim dtTimeStampAktuell As DateTime = System.DateTime.Now
Dim iSekunden As Integer = dtTimeStampAktuell.Second
If iSekunden = 50 Then  
  If Not TimerDatenAbruf.Enabled Then ' only start, if it don't already runs
    TimerDatenAbruf.Start() ' start the ten second timer
    TimerSekundenSync.Stop() ' stop the one second timer
   End If
End If
End Sub

As I already wrote above, the ten second timer is started correct with the above code (at 50 seconds), but don't fire the firs time to the next minute and then loose further time as longer as it run's.
I hope somebody here can bring some light I the dark.
Thanks in advance for any hint.

Update (especially to @dbasnett):
I have implemented your code in the Windows-Client:
Result:
enter image description here

On the left side, you can see my ("old") code with the timers, on the right side you can see your code. Both have started correct ( 1 ) and ( 2 ) and are showing the correct seconds after 12 Minutes ( 1.1 ) and ( 2.1 ) and also after 21 Minutes ( 1.2 ) and ( 2.2 )
As I worte, the "timer solution" in the windows client also run's correct after full 9 hours.
In the windows service the timer starts to drift after about 16 minutes...
So.. something is different regarding timers in an windows client and a windows service
I then have tried to implement your solution in the windows service (to give it a try in a windows service) and had several issues with it:
enter image description here

The data are queried over an async Json API with httpclient.
Therefore the call has to be awaited ( 1 ) and also the sub has to be async ( 2 )
As soon as I make the sub async, I'm not able to define the static variables ( 3 ) and ( 4 )
Further Me.BeginInvoke ( 5 ) is not available in a windows service (only in in a windows forms application).
So... if you are able to give me the correct code for a windows service, I will implement and test it...
Thanks!

Update #2 (especially to @dbasnett):

enter image description here The data are queried in an async function: NasdaqDatenAbrufen() as task ( 1 )

enter image description here

I cannot await the task ( 2 )
I think of cause the "await t" enter image description here

Without await, VS says, the task will not be awaited...

Thanks

FredyWenger
  • 2,236
  • 2
  • 32
  • 36
  • The comments on and answers to [Windows Service System.Timers.Timer not firing](https://stackoverflow.com/questions/8466597/windows-service-system-timers-timer-not-firing) may be useful for you. – Andrew Morton Aug 17 '23 at 11:08
  • Thanks: I have read this positing before post my own. My problem is not, that the timer don't fire, my problem is, that the INTERVALL (10 seconds) does not work correct, especially the first time. – FredyWenger Aug 17 '23 at 12:03
  • It seems that your program may need a re-design. However, with the code that you posted, it's difficult to know. – Tu deschizi eu inchid Aug 17 '23 at 15:42
  • The following may be of interest: [Worker services in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/workers?pivots=dotnet-7-0) and [PeriodicTimer](https://learn.microsoft.com/en-us/dotnet/api/system.threading.periodictimer?view=net-7.0) – Tu deschizi eu inchid Aug 17 '23 at 15:54

1 Answers1

2

If you are examining the system date time every 10 seconds based on a Timer you should expect to see a drift. I don't understand why you need two timers to get one timer with what you want.

The truth is that you have to debug code like this as if it will run the entire handler uninterrupted but knowing it could be interrupted at the conclusion of every instruction.

Try the following code as a standalone app and see if it helps. It tries to keep the DateTime.Now.Second an even multiple of 10. In case you can't tell it is a Windows Form app.

Public Class Form1
    Private Shared ReadOnly InitInterval As Integer = CInt(TimeSpan.FromMilliseconds(5).TotalMilliseconds)
    Public WithEvents TenSecTimer As New System.Timers.Timer(InitInterval)
    Private Sub TenSecTimer_Elapsed(sender As Object,
                                     e As Timers.ElapsedEventArgs) Handles TenSecTimer.Elapsed
        Static stpw As Stopwatch = Stopwatch.StartNew
        stpw.Stop()
        Debug.Write("* ")
        Debug.WriteLine(stpw.ElapsedMilliseconds.ToString("n0"))
        Static ProdInterval As Integer = CInt(TimeSpan.FromSeconds(10).TotalMilliseconds)
        Static drifted As Boolean = False
        Const driftVal As Long = 300 ' 50 for testing, larger for production i.e. 250.  not more than 500
        Dim dtNOW As DateTime = DateTime.Now
        If TenSecTimer.Interval = InitInterval Then
            Dim sec As Integer = 60 - dtNOW.Second
            If sec <= 1 Then
                Threading.Thread.Sleep(57 * 1000) '57 seconds
            End If
            dtNOW = DateTime.Now
            sec = 60 - dtNOW.Second
            If sec <> 1 Then
                TenSecTimer.Stop()
                'when is second 00, hold until it is
                sec = 950 * sec
                Threading.Thread.Sleep(sec)
                sec = 0
                Do While DateTime.Now.Second <> 0
                    sec += 1
                Loop
            End If
            ' Debug.WriteLine(sec.ToString("n0"))
            TenSecTimer.Interval = ProdInterval
            TenSecTimer.Start()
        ElseIf drifted Then
            'reset interval
            drifted = False
            TenSecTimer.Interval = ProdInterval
        ElseIf dtNOW.Millisecond >= driftVal Then
            'decrease interval
            drifted = True
            TenSecTimer.Interval = ProdInterval - driftVal
        ElseIf dtNOW.Second Mod 10 <> 0 Then
            drifted = True
            TenSecTimer.Interval = ProdInterval + driftVal
        End If
        ' do real work on a different thread!!!
        Dim t As Task
        t = Task.Run(Sub()
                         '   query the data over the API and store the data on the SQL server
                         Me.BeginInvoke(Sub()
                                            Label1.Text = DateTime.Now.ToString("HH:mm:ss.fff")
                                        End Sub)
                     End Sub)
        stpw.Restart()
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TenSecTimer.Start()
        Debug.WriteLine("")
        Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff"))

    End Sub
End Class

edited code to fix service errors

Private Shared ReadOnly InitInterval As Integer = CInt(TimeSpan.FromMilliseconds(5).TotalMilliseconds)
Public WithEvents TenSecTimer As New System.Timers.Timer(InitInterval)
Private stpw As Stopwatch = Stopwatch.StartNew
Private ProdInterval As Integer = CInt(TimeSpan.FromSeconds(10).TotalMilliseconds)
Private drifted As Boolean = False
Private Async Sub TenSecTimer_Elapsed(sender As Object,
                                 e As Timers.ElapsedEventArgs) Handles TenSecTimer.Elapsed
    stpw.Stop()
    Debug.Write("* ")
    Debug.WriteLine(stpw.ElapsedMilliseconds.ToString("n0"))
    Const driftVal As Long = 300 ' 50 for testing, larger for production i.e. 250.  not more than 500
    Dim dtNOW As DateTime = DateTime.Now
    If TenSecTimer.Interval = InitInterval Then
        Dim sec As Integer = 60 - dtNOW.Second
        If sec <= 1 Then
            Threading.Thread.Sleep(57 * 1000) '57 seconds
        End If
        dtNOW = DateTime.Now
        sec = 60 - dtNOW.Second
        If sec <> 1 Then
            TenSecTimer.Stop()
            'when is second 00, hold until it is
            sec = 950 * sec
            Threading.Thread.Sleep(sec)
            sec = 0
            Do While DateTime.Now.Second <> 0
                sec += 1
            Loop
        End If
        ' Debug.WriteLine(sec.ToString("n0"))
        TenSecTimer.Interval = ProdInterval
        TenSecTimer.Start()
    ElseIf drifted Then
        'reset interval
        drifted = False
        TenSecTimer.Interval = ProdInterval
    ElseIf dtNOW.Millisecond >= driftVal Then
        'decrease interval
        drifted = True
        TenSecTimer.Interval = ProdInterval - driftVal
    ElseIf dtNOW.Second Mod 10 <> 0 Then
        drifted = True
        TenSecTimer.Interval = ProdInterval + driftVal
    End If
    ' do real work on a different thread!!!
    Dim t As Task
    t = Task.Run(Sub()
                     '   query the data over the API and store the data on the SQL server
                 End Sub)
    Await t
    stpw.Restart()
End Sub

edit 2 - partial

    ' do real work on a different thread!!!
    Await NasdaqDatenAbrufen()
    'Dim t As Task
    't = Task.Run(Sub()
    '                 '   query the data over the API and store the data on the SQL server
    '             End Sub)
    'Await t
    stpw.Restart()
dbasnett
  • 11,334
  • 2
  • 25
  • 33
  • Have you read my entire post? As I have wrote, the logic works like a charm in the windows client (starts correct to .00 and hold the ten seconds for the whole day). So the code in the windows client works fully as expected. The problem is, that the same code don't works similar in the windows SERVICE. So something seems to be different here and I don0t know what... – FredyWenger Aug 19 '23 at 11:39
  • I did read your entire post. Did you read mine? Did you try my code in the windows service? – dbasnett Aug 21 '23 at 13:19
  • @FredyWenger - I'm actually shocked that your timer would fire on a mod 10 second all day, my code certainly doesn't without the drift code. – dbasnett Aug 21 '23 at 13:23
  • As I wrote, the code works without any problem in the windows client. Start at 00 and then fires any 10 seconds. The same code don't work correct in a windows service and I want to know, what may be DIFFERENT in the windows service compared to the windows client (I can't see any logical reason). I further don't see any problem to fire a timer each 10 seconds. So... if you logical can explain the DIFFERENCE of a Timer in a windows service to timer in a windows client, I will try your code;-) – FredyWenger Aug 21 '23 at 15:41
  • @FredyWenger - You're assuming that your code doesn't drift in the client, and as I pointed out I think it probably does drift. My client does drift. By drift I mean the event fires every 10 seconds plus a few milliseconds. Those few milliseconds add up. You can test what I wrote easily. If the stpw elapsed is 10 seconds exactly I'd be shocked. If client is drifting then the service is drifting. The code I provided deserves a test. – dbasnett Aug 21 '23 at 16:18
  • Sorry, but you still don't get the problem., I have. I start the 10 second timer in the windows service at e.g. 10:05:50 and the first tick fires at 10:06:03. So I have a delay of about 3 seconds. THAT'S the main problem (with the windows client it fires at 10:06:00 (as it should be). The timer IS started at 10:05:50 (I know that, as I write an event log entry) but then fires a few (about 3) seconds later. – FredyWenger Aug 21 '23 at 18:37
  • 1
    @FredyWenger - if you won't try my code I don't know what to tell you. You persist in a belief that the timer will fire in 10.000 seconds. In my testing with a client it never did. Good luck. – dbasnett Aug 23 '23 at 13:08
  • I have updated my first posting. Please have a look. Thanks! – FredyWenger Aug 28 '23 at 12:16
  • 1
    @FredyWenger - posted code to fix the errors. – dbasnett Aug 28 '23 at 13:14
  • -> see update #2 in my posting -> still a problem... – FredyWenger Aug 28 '23 at 17:02
  • 1
    @FredyWenger - see edit 2 – dbasnett Aug 28 '23 at 20:12
  • @dbasenett: Thanks! I have changed the windows service now finally and updated it on the server. I will run today the first time and hopefully store the data now correct any 10 seconds on the SQL server. I will let you know the results... Thanks again for your efforts. – FredyWenger Aug 29 '23 at 11:46
  • Hope it goes well. Good luck! – dbasnett Aug 29 '23 at 13:06
  • 1
    @dbasenett: I works:-). I have updated my question and set your answer as solution. I still don't know, why the timer code don't work in a windows service, but now has a solution as needed with your code. Finally many thanks! – FredyWenger Aug 30 '23 at 11:49