How to execute code after a delay.
There are different methods to execute code after a delay or execute it asynchronously, after a delay or not. Here, I'm considering a Timer and simple implementations of the Async/Await pattern.
A loop that calls Application.DoEvent()
should be avoided in any case.
► Using a Timer to delay the execution of a single instruction or the code in one or more methods:
You cannot await for a Timer, but you can use a method that creates a Timer and executes an Action when the Timer raises its event, signaling that the Interval specified has elapsed.
The method that follows accept as arguments a delay
value and an Action delegate.
The Delay is used to set the Timers' Interval, the Action represent the code that will be executed when the Timer Ticks (I'm using a System.Windows.Forms.Timer here, since you seem to refer to a WinForms application).
Private waitTimer As New System.Windows.Forms.Timer()
Private TimerTickHandler As EventHandler
Private Sub Wait(delay As Integer, action As Action)
waitTimer.Interval = delay
TimerTickHandler = New EventHandler(
Sub()
action.Invoke()
waitTimer.Stop()
RemoveHandler waitTimer.Tick, TimerTickHandler
End Sub)
AddHandler waitTimer.Tick, TimerTickHandler
waitTimer.Start()
End Sub
We can call this method when we need to execute code after a delay.
The Action can be a simple instruction: in this case, the Text of label1
will be set to "Done" after 5 seconds, while the UI Thread continues its operations:
label1.text = "About to Start..."
Wait(5000, Sub() Me.label1.Text = "Done")
The Action can also be a method:
Private Sub SetControlText(control As Control, controlText As String)
control.Text = controlText
End Sub
' Elsewhere
Wait(5000, Sub() SetControlText(Me.label1, "Done"))
Of course the SetControlText()
method can execute more complex code and, optionally, set a Timer itself, calling Wait()
.
► Using the Async/Await pattern.
An async method provides a convenient way to do potentially
long-running work without blocking the caller's thread. The caller of
an async method can resume its work without waiting for the async
method to finish.
In simple terms, adding the Async modifier to a method, allows to use the Await operator to wait for an asynchronous procedure to terminate before the code that follows is executed, while the current Thread is free to continue its processing.
▬ Note that the Async
modifier is always applied to a Function()
that returns a Task
or a Task(Of something)
(something can be any value/reference a method can return).
It's only applied to a Sub()
when the Sub
(void
) method represents an Event Handler
.
This is very important to remember and apply without exceptions (unless you're quite aware of the implications). ▬
Read the Docs about this (in the previous link) and these:
Async and Await
Don't Block on Async Code
A simple Async
method can be used to delay the execution of an action:
Private Async Function Wait(delay As Integer, action As Action) As Task
Await Task.Delay(delay)
action?.Invoke()
End Function
This is similar to the Timer functions and acts in a similar way. The difference is that you can Await
this method to both execute the code it runs asynchronously and to wait for its completion to run other code after the method returns.
The calling Thread (the UI Thread, here), will continue its operations while the Wait()
method is awaited.
Assume that, in a Button.Click
handler, we want to execute an Action (a method that doesn't return a value) or a Function (a method that returns a value) and the code that follows should execute only after this Action or Function returns:
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
Await Wait(5000, New Action(Sub() Me.label1.Text = "Done"))
Await Wait(5000, Nothing)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
Here, we instruct to wait for 5 seconds, then set the Text of a Label to a value, wait other 5 seconds, doing nothing, then execute the code that follows
If we don't need to perform an Action, we can simply use Task.Delay():
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Delay(5000)
label1.Text = "Done"
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
We can also use Async/Await to wait for the completion of a Task run in a ThreadPool Thread, calling Task.Run() to execute code in a Lambda expression:
(just an example, we shouldn't use a ThreadPool Thread for such a simple task)
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Run(Async Function()
Await Task.Delay(5000)
BeginInvoke(New Action(Sub() Me.label1.Text = "Done"))
End Function)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
See also the Task.ContinueWith() method:
Creates a continuation that executes asynchronously when the target
Task completes.