-1

I have a windows forms timer with an async callback that await a database operation, why the await is executed again even before the previous callback has not finished yet? How can I fix this? Many people say that System.Windows.Forms.Timer wait that previous callback operation finishes before executing new one.

Private Async Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Timer1.Tick 
  ' I have an array with 6 elements that i control 
  ' for executing database operation     
  For i=1 To 6
    If(some condition is true in array index)
      Await SaveAsync()
      'set to false the condition in array index after save
    End If
  Next       
End Sub

Thanks in advance.

Alfons Deda
  • 33
  • 1
  • 9
  • What does `SaveAsync` do? If it's too slow, the timer will fire again before `SaveAsync` had a chance to finish – Panagiotis Kanavos Nov 15 '22 at 11:26
  • @PanagiotisKanavos would be better to not use async await in timers or there is another way to correct this? – Alfons Deda Nov 15 '22 at 14:13
  • It doesn't matter. The timer will fire anyway. What does `SaveAsync` do? How often does the timer fire? – Panagiotis Kanavos Nov 15 '22 at 14:46
  • Insert data in database every second. – Alfons Deda Nov 15 '22 at 14:52
  • Post the code. There's no way a single INSERT VALUES would take 1 second, unless something is very wrong - complex query, missing indexes, blocking from other connections – Panagiotis Kanavos Nov 15 '22 at 15:01
  • What you're showing here isn't a callback, it's an event handler. It runs asynchronously in its own way (even setting aside the `Async` and `Await`). The underlying behavior is that when the timer fires, it adds a message to the application Windows message queue, and when the message pump sees the message, it triggers your event procedure. With that knowledge (and the knowledge of what `Async` and `Await` actually do) you should be able to deduce what is going on (sort of alluded to in the answer but not explicitly stated). – Craig Nov 18 '22 at 15:37
  • 1
    @Craig I read that System.Windows.Forms.Timer was the only timer that finish the previous tick before starting new one and I tought that would work even with async await but i was wrong, so I created a task variable that hold the task to be awaited and in the next line i did await task, and I control the task state in every timer tick verifying his state until is finished. – Alfons Deda Nov 18 '22 at 15:53
  • If you don't have an `Await` (or other statement that pumps the message queue), that would necessarily be correct, because the window procedure is blocked while your event handler is running. I just checked the online help for System.Windows.Forms.Timer, and I don't see a statement like that in the Microsoft documentation. – Craig Nov 18 '22 at 15:59
  • https://stackoverflow.com/questions/10471315/what-if-a-timer-can-not-finish-all-its-works-before-the-new-cycle-time-arrives. – Alfons Deda Nov 18 '22 at 16:02
  • Yep, that's kind of the same thing. The significant point is that it contrasts to the timers which will spin up a new thread to service a tick; the Windows Forms timer operates with a tick message posted into the app message queue, so it will be blocking *provided that the event handler does not do something that pumps the message queue*. I added a comment on the answer, since it seems like it's a potential point of confusion. – Craig Nov 18 '22 at 17:00

2 Answers2

1

It is presumably because the asynchronous call takes longer than the Interval of your Timer. That would mean the Tick event would be raised again before the previous event handler has finished executing. If what you want is a specific interval between when the previous call has completed and the next event then you should stop the Timer, make the call and then start it again:

Private Async Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Timer1.Tick 
    Timer1.Stop()
    Await SaveAsync()
    Timer1.Start()
End Sub
jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
0

The other answer tells you how to fix the problem, but it doesn't explain what's going wrong.

There is a much older Stack Overflow question about the timer options, and it makes a possibly misleading statement about the timer tick waiting. It might be more helpful to discuss the potential implementations of a timer tick:

  • A new thread can be spun up to service the tick immediately
  • A notification can be posted which is processed at a later time

System.Windows.Forms.Timer does the latter. In particular, it posts the notification as a Windows message, and when the standard message pump sees the message, it raises a .NET event which is processed on the UI thread.

In most cases, this will mean that processing one tick will prevent anything from happening for the next tick, because the message pump is blocked while the event handler executes.

This only holds as long as the message pump is blocked, though. There are two likely ways it might become unblocked:

  • Code to explicitly process messages, like a call to Application.DoEvents or equivalent code written directly against the dispatcher
  • Await will do this implicitly, as it returns control to the UI will it waits for the asynchronous call to finish. This is by design and is one of the intended use cases for Await, to allow the UI to be responsive while the asynchronous call is executing.

As in any case where you use Await, you need to be prepared for re-entrancy and design your code accordingly.

Craig
  • 2,248
  • 1
  • 19
  • 23