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
Related
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.
I can get a progress bar to increment inside the Do loop but it massively affects the speed of the loop. How can i keep track of progress of the Do Until loop without affecting it?
Do Until temp = pbTemp2
temp+= 1
progressbar1.increment(1) '<--- i dont want this in here but still need to track the progress
loop
The middle ground between what Visual Vincent proposed and a BackgroudWorker.
(I'm assuming this is related to .NET WinForms).
Crate a Thread to perform some work, and use a SynchronizationContext to queue the results the process to the UI context.
The SynchronizationContext will then dispatch an asynchronous message to the synchronization context through a SendOrPostCallback delegate, a method that will perform its elaboration in that context. The UI thread in this case.
The asynchronous message is sent using SynchronizationContext.Post method.
The UI thread will not freeze and can be used to perform other tasks in the meanwhile.
How this works:
- Define a method which will interact with some UI control:
SyncCallback = New SendOrPostCallback(AddressOf Me.UpdateProgress)
- Initialize a new Thread, specifying the worker method that the thread will use.
Dim pThread As New Thread(AddressOf Progress)
- Starting the thread, it's possible to pass a parameter to the worker method, in this case is the maximum value of a progress bar.
pThread.Start(MaxValue)
- When the worker method needs to report back its progress to the (UI) context, this is done using the asychronous Post() method of the SynchronizationContext.
SyncContext.Post(SyncCallback, temp)
Here, the thread is started using a Button.Click event, but it could
be anything else.
Imports System.Threading
Private SyncContext As SynchronizationContext
Private SyncCallback As SendOrPostCallback
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button11.Click
SyncContext = WindowsFormsSynchronizationContext.Current
SyncCallback = New SendOrPostCallback(AddressOf Me.UpdateProgress)
Dim MaxValue As Integer = ProgressBar1.Maximum
Dim pThread As New Thread(AddressOf Progress)
pThread.IsBackground = True
pThread.Start(MaxValue)
End Sub
Private Sub UpdateProgress(state As Object)
ProgressBar1.Value = CInt(state)
End Sub
Private Sub Progress(parameter As Object)
Dim temp As Integer = 0
Dim MaxValue As Integer = CType(parameter, Integer)
Do
temp += 1
SyncContext.Post(SyncCallback, temp)
Loop While temp < MaxValue
End Sub
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.
I am trying to implement multi-threading into an app. The app needs to create a variable number of threads whilst passing variables across. I can easily create the threads, however I am trying to figure out a way to be able to stop all threads at once and if an error is caught in any one of these threads, stop all of them.
My current solution is to enclose the functions in a loop that checks if a boolean value is "True", in which case the thread carries on. If there is an error, I change the value to "False" and all the threads stop. Similarly if I want to stop the threads manually I can set the value to "false" from a function.
Is there a better solution to this, as the main issue is the threads must reach the end of the loop before they stop completely?
Try this
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim foo As New List(Of Threading.Thread)
Threading.Interlocked.Exchange(stopRun, 0L)
For x As Integer = 1 To 5 'start five threads
Dim t As New Threading.Thread(AddressOf workerThrd)
t.IsBackground = True
t.Start()
foo.Add(t) 'add to list
Next
Threading.Thread.Sleep(2000) 'wait two seconds
Threading.Interlocked.Increment(stopRun) 'signal stop
For Each t As Threading.Thread In foo 'wait for each thread to stop
t.Join()
Next
Debug.WriteLine("fini")
End Sub
Dim stopRun As Long = 0L
Private Sub workerThrd()
Do While Threading.Interlocked.Read(stopRun) = 0L
Threading.Thread.Sleep(10) 'simulate work
Loop
Debug.WriteLine("end")
End Sub
Running the threads in a while True block should be fine. Once its false, you could just iterate over the threads and call thread.abort() even though sometimes using abort isnt a good idea. Using a list of threads could be helpful. I dont know how you are creating your threads but this should be easy to understand.
Dim listThreads As List(Of Threading.Thread)
'create/instantiate your threads adding them to the collection something like the following
For i = 1 To numberofthreadsyouneed
Dim tempThread As Threading.Thread = New Threading.Thread
tempThread.Start()
tempThread.Add(tempThread)
next
Instead of using a while block just do a Try catch. inside the catch iterate over the list to abort the threads
Catch ex As Exception
For each Thread in listThreads
Thread.Abort()
Next
end Try
If you want more control
Here
is a pretty sweet thing called Tasks that they come out with a while back. It gives you a little more control over your threads
Hope this helps.