For years I create delays in my software using, for example:
Wait(10000)
Sub Wait(milliseconds)
<here I get the current time>
Do
<here I loop until the current time passed in seconds and then exit this loop>
Application.DoEvents()
Loop
End Sub
The problem is, this uses a lot of CPU. I tried Thread.Sleep(1000), but this FREEZES my application while it's performing!
I tried using a Timer, but I STILL need a loop that doesn't freeze yet acts like Application.DoEvents(). It seems impossible.
My goal is to do this:
label1.text = "ok about to start"
Wait(5000)
' the following line CAN NOT run until after 5 seconds.
label1.text = "DONE"
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.
Related
I, at most, have two threads running at any single time. How do I wait for these threads to finish before executing my next step?
If I don't wait for them, I get a NullReferenceException when I check the values because they haven't been set yet due to the threads still running.
I would go with the Async / Await pattern on this. It gives you excellent flow control and won't lock up your UI.
Here is a great example from MSDN:
Public Class Form1
Public Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim tasks As New List(Of Task)()
tasks.Add(Task.Run(addressof Task1))
tasks.Add(Task.Run(addressof Task2))
Await Task.WhenAll(tasks)
MsgBox("Done!")
End Sub
Private Async Function Task1() As Task 'Takes 5 seconds to complete
'Do some long running operating here. Task.Delay simulates the work, don't use it in your real code
Await Task.Delay(5000)
End Function
Private Async Function Task2() As Task 'Takes 10 seconds to complete
'Do some long running operating here. Task.Delay simulates the work, don't use it in your real code
Await Task.Delay(10000)
End Function
End Class
The basic idea is to create an array of Task (these can point to functions that return Task also). This queues up the "threads" wrapped in task objects that get run when you call Task.WhenAll, which will execute all the tasks in the array and continue after they all complete. Code after that will run once every tasks completes, but it won't block the UI thread.
If you call join the main Thread will be wait that the other thread finished. I think the code bellow will be good to understand the idea.
Sub Main()
thread = New System.Threading.Thread(AddressOf countup)
thread.Start()
thread2 = New System.Threading.Thread(AddressOf countup2)
thread2.Start()
thread.Join() 'wait thread to finish
thread2.Join() 'wait thread2 to finish
Console.WriteLine("All finished ")
Console.ReadKey()
End Sub
See the links:
General;
WinForms
I have a small VB.Net project with link to sql using web service (SOAP).
I have to make sure that all forms are totally responsive no matter what, and it's working pretty well. My only problem is on loading the application!
The main start-up form has only single line of code:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await objWebService.GetCurrentSessionsAsync
End Sub
But while this "awaitable" code is being executed the form is unresponsive, frozen and wait cursor is displayed.
Any idea on what might be causing this issue and how to handle it?
In regard to your answer, the code can be much cleaner if you don't combine different programming patterns, check this out:
Private Async Sub frmMain_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Dim res = Await GetCurrentSessionsAsync()
End Sub
Private Async Function GetCurrentSessionsAsync() As Task(Of com.services.Server)
Try
Return Await Task.Factory.
StartNew(Function() objWebService.GetCurrentSessions)
Catch ex As Exception
Glob.ErrorLog("GetCurrentSessions", ex, True)
Return New com.services.Server
End Try
End Function
References:
try-catch (C# Reference)
Async Return Types (C# and Visual Basic)
The key problem is that Async does not magically make your method asynchronous. It only lets compiler know that your method will have Await keywords, and that the code needs to be converted into a state machine. Any code that is not awaited is executed synchronously, even if the method is marked as Async. Consider the following example:
Private Async Sub Form1_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Await LongRunning1() 'opens the form, then asynchronously changes
'Text property after 2 seconds
End Sub
Private Async Function LongRunning1() As Task
Await Task.Factory.StartNew(Sub() Threading.Thread.Sleep(2000))
Me.Text = "Finished LongRunning1"
End Function
Here a long running process, Thread.Sleep as an example, is wrapped into a Task, and there is an Await keyword. It tells the compiler to wait for the statements inside the task to finish, before executing the next line. Without the Await, the Text property would be set immediately.
Now suppose you have some long running synchronous code in your Async method:
Private Async Sub Form1_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Await LongRunning2() 'synchronously waits 2 seconds, opens the form,
'then asynchronously changes Text property after 2 seconds
End Sub
Private Async Function LongRunning2() As Task
Threading.Thread.Sleep(2000)
Await LongRunning1()
Me.Text = "Finished LongRunning2"
End Function
Notice in this case it synchronously waits for the Thread.Sleep to finish, so for the end user you app appears as hanging. Bottom line is - you have to know which method calls can be long running, and wrap them into a task based await model. Otherwise you may be seeing the problem you are seeing.
If this sounds too complicated, you can fire up a background worker (.NET 2.0+), or use TPL (.NET 4.0+) to start a task. If you wish to go into lower level, threading is available since .NET 1.1. Then display some wait/progress window/overlay on top of the form/control, for which the functionality is not yet available. Check these out:
Loading data from DB asynchronously in win forms
Await async with event handler
Thanks to #PanagiotisKanavos for pointing me in the right direction.
So here what it is (I have to say that the answer of Neolisk and Panagiotis led me to the solution):
What made my loading form unresponsive is what appeared to be a bug in web services, only the first call of my web service would produce this issue. So If the first call was made after form load, on another event, I would face same problem.
To fix this, I changed the way I call my first method through web service using TaskCompletionSource variable, and calling my first method using Thread. I'll post my before/after code to be sure I delivered my fix clearly.
Before:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim res = Await objWebService.GetCurrentSessionsAsync
End Sub
After:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim res = Await GetCurrentSessionsAsync()
End Sub
Dim _tcServer As New TaskCompletionSource(Of MyProject.com.services.Server)
Private Async Function GetCurrentSessionsAsync() As Task(Of com.services.Server)
Try
Dim th As New System.Threading.Thread(AddressOf GetCurrentSessions)
th.Start()
Return Await _tcServer.Task
Catch ex As Exception
Return New MyProject.com.services.Server
End Try
End Function
Private Sub GetCurrentSessions()
Try
Dim res = objWebService.GetCurrentSessions
_tcServer.SetResult(res)
Catch ex As Exception
Glob.ErrorLog("GetCurrentSessions", ex, True)
End Try
End Sub
I hope this can help others in the future.
Thank you.
While I have some VBScript experience, this is my first attempt at creating a very simple VB.NET (Windows Forms Application) wrapper for a command line application. Please be kind!
I have a very simple GUI with two buttons that both do an action and I'd like to show a marquee progress bar until the action (read: the process) is complete (read: exits).
The 'save' button does this:
Dim SaveEXE As Process = Process.Start("save.exe", "/whatever /arguments")
From there I'm starting the marquee progress bar:
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I thought I could use SaveEXE.WaitForExit() but the Marquee starts, then stops in the middle until the process exits. Not very useful for those watching; they'll think it hung.
I thought maybe I could do something like this but that causes my VB.Net app to crash
Do
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
Loop Until SaveEXE.ExitCode = 0
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I'm not entirely sure what needs to be done, short of getting some formal training.
You can use the new Async/Await Feature of .NET 4.5 for this:
Public Class Form1
Private Async Sub RunProcess()
ProgressBar1.Visible = True
Dim p As Process = Process.Start("C:\test\test.exe")
Await Task.Run(Sub() p.WaitForExit())
ProgressBar1.Visible = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RunProcess()
End Sub
End Class
Note the Async keyword in the declaration of the RunProcess sub and the Await keyword.
You run the WaitForExit in another thread and by using Await the application basically stops at this line as long as the task takes to complete.
This however also keeps your GUI reponsive meanwhile. For the example I just show the progressbar (it is invisible before) and hide it once the task is complete.
This also avoids any Application.DoEvents hocus pocus.
Is it possible to invoke a function from a "non main" thread and then wait for it to finish before executing the rest?
I could set a boolean just before and then make the function "flip" the boolean to false when its done, but I wondered if there was a simplier/more professional way of achieving this?
Thanks
I guess you want to keep your form responsive yet you don't want to have extra procedures being called or stuff like that.
In that case the Async and Await keywords are probably a good way for you:
It is explained in detail here http://msdn.microsoft.com/en-US/en-en/library/hh191443.aspx but I will give you a short overview:
You first declare a method with the Async keyword. This can be for example the method handling a button click event in my example below.
In this method you get your result from another method that is called as a task and assign this to a temporary variable using the await keyword.
During the time the task is running the further execution of the code is halted. Your GUI stays responsive tough.
Here is a small example (just throw a Button and a Label on a Form):
Public Class Form1
''' <summary>
''' This method does the work. It is called from the async method in form of a Task(Of String).
''' </summary>
Private Function GetString() As String
'Some delay
Threading.Thread.Sleep(3000)
Return "Hello World!"
End Function
'Note the Async Keyword
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'First create the task
Dim t As Task(Of String) = New Task(Of String)(AddressOf GetString)
'Start the task
t.Start()
'Wait for the task to complete. Does not suspend your GUI!
'Much preferrable to some kind of waiting loop with DoEvents and stuff.
Dim Result As String = Await t
'Signal the end
MsgBox("DONE")
'Output the results
Label1.Text = Result
End Sub
End Class
I'll be honest, I can't really dive into the dark underbellies of how this is actually implemented in the .NET Framework since I have not read much about it myself. (I program mainly for the .NET Framework 4.0. Async/Await was introduced in 4.5. It can however be used in 4.0 as well with an extension package by Microsoft https://www.nuget.org/packages/Microsoft.Bcl.Async).
The actual usage however is not that hard as you can see, so I think this is the way to go.
In VB .NET, when you call RaiseEvent X(), is the function that handles the event X processed asynchronously or synchronously. I was under the impression that RaiseEvent and the processing of the event were Synchronous unless created explictly on another thread. I've been told otherwise though.
Events are raised synchronously by default. Since MulticastDelegates are designed to support asynchronous invocation it is possible to invoke the delegates in an event's invocation list asynchronously but this is not the default behavior.
I just did some testing also...
Public Sub MyHandler() Handles Complete
MsgBox("My Handler - Beginning 5 second sleep")
Threading.Thread.Sleep(5000)
MsgBox("My Handler - Awoken")
End Sub
Public Sub SomeFunction()
MsgBox("Some function - Raising Event")
RaiseEvent Complete()
MsgBox("Some function - After Event")
End Sub
Output:
Some function - Raising Event
My Handler - Beginning 5 second sleep
My Handler - Awoken
Some function - After Event