So I am playing with Multi Threading. I have a specific form that takes quiet a while to pop up as it is loading data from file. My idea was to have the data load in the background but still display the form and have a message on the form showing that additional data is loading.
I have everything done, except I am not sure how to get notified the thread is done and is ready to pass me the data as a Dictionary.
This is as far as I have gotten :P
Dim t1 As Task(Of Dictionary(Of String, Double()))
Private Sub cbchannels_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cbchannels.SelectedIndexChanged
t1 = Task(Of Dictionary(Of String, Double())).Factory.StartNew(Function() Load_Data())
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If t1.IsCompleted Then
data = t1.Result
End If
End Sub
I know that the task has the ability to check if its completed and to get the results
t1.IsCompleted
t1.Result
Is using a timer to continuously check if the task is completed the only way?
And is what I am doing makes sense?
I do not want to use a BackgroundWorker.
That timer is very clever. Fortunately there is a direct was to get notified. You can call ContinueWith on any Task to register a callback that is called when that task completes. This answers your question as asked.
Instead you should be looking into async and await. Running background work in UI apps has gotten a lot easier with C# 5.
While it's not the only way, the simplest option may be to use a BackgroundWorker. You call RunWorkerAsync to set it off and do the work in the DoWork event handler. When you're done, assign the data to the e.Result property. You can then get that back on the UI thread in the RunWorkerCompleted event handler. E.g.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim table As DataTable = GetData()
e.Result = table
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Dim table = DirectCast(e.Result, DataTable)
'Use table here.
End Sub
Related
I have come to learn that a module level variable's value will not be altered until a sub routine that changed it exits.
StopBackgroundWorker1 = True
Thread.Sleep(1500)
If BackgroundWorker1Complete = False Then
Exit Sub
End If
in this example, I added a long delay for testing. I'm simply trying to stop and start a background worker safely with vb 2017 new background worker class.
The example above with "StopBackgroundWorker1 = True", I was hoping to stop the worker at a safe place and then continue within that sub with other code.
But what is happening is that the "StopBackgroundWorker1 = True" is not being set "True" until the sub exits.
There must be another way to do what I am trying to do, please help
Ok here is a complete example,
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Private flag As Boolean = False
Dim Completed As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
flag = True
'Do
' do loop never see's a true flag
'Loop Until Completed
Thread.Sleep(500)
If Completed = True Then
Label1.BackColor = Color.Red
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Do
Thread.Sleep(25)
Loop Until flag
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As
RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Completed = True
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles
Button2.Click
Label1.Text = flag.ToString
End Sub
End Class
Now the concept is if you hit button1 and wait for background worker to complete, it should turn lable1 red. but it doesn't. The do loop looking for a true flag will spin forever locking the form up.
I have determined with this example that the flag is not set to true until you exit the sub. Hit Button1 again and lable1 turns red.
Thanks in advance for any answers.
This doesn't answer your question per se but I want to post a large code snippet so I'll post it as an answer. It demonstrates that what you think is the problem is not the problem, i.e. that a field's value changes as soon as you change it, even if that change is made from a BackgroundWorker.DoWork event handler.
Create a new Windows Forms application project, add a Button, a Label and a BackgroundWorker to your form and then paste in this code over the default code of the form:
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Private flag As Boolean
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Label1.Text = flag.ToString()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Thread.Sleep(5000)
flag = True
BackgroundWorker1.ReportProgress(0)
Thread.Sleep(5000)
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Label1.BackColor = Color.Green
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.BackColor = Color.Red
End Sub
End Class
Run the project and, when the form appears, start clicking the Button at a pace of a few times per second. You'll see that the value of the flag field, as displayed on the Label, changes from False to True as soon as the code to set it is executed in the DoWork event handler. The Label will turn green when that happens, so it's easy to spot. You'll know that it didn't wait until the DoWork event handler completes because the Label will turn red at that point.
EDIT: Now that you have provided all the relevant information, the issue is obvious. As I have already said, the moment you set a variable, that is the value of that variable. There's no waiting because there cannot be any waiting because there's nowhere to store a temporary value for the variable.
The reason that it looks otherwise is that your test code is faulty. If you use the debugger then you will see how. When you use a BackgroundWorker, the DoWork event handler is executed on a secondary thread but the RunWorkerCompleted event handler is executed on the UI thread. That means that your DoWork event handler can execute at the same time as your Click event handler for Button1 because they are on different threads, but the RunWorkerCompleted event handler cannot run at the same time, so it has to wait until the Click event handler completes before it can be executed. That means that the code to set the Completed field doesn't get executed until the Click event handler completes. It's not that the field value doesn't change when it's set but rather that it doesn't actually get set. If you place breakpoints on the two lines that access that Completed field then you'll see that.
The mistake you're making is trying to do something in that Click event handler after the DoWork event handler completes. That's wrong. That's exactly what the RunWorkerCompleted event handler is for. That's where you do UI work after the background work completes.
Also, you can get rid of that flag variable. Cancellation functionality is built into the BackgroundWorker class. Look at the CancelAsync method and the CancellationPending property.
Many thanks to "jmcilhinney" for his insights! I have figured out the code I was looking for!
This code allows me to stop and start a background thread safely by allowing the background thread to finish completely before restarting.
During the time that the background thread is stopped, user actions can perform operations without the worry of cross-threading or with thread.abort garbled code conditions.
Finally, stress-free threading!
I wish I could find the doc on MSDN that I read that stated the await async method was far superior to task.run but that's another argument.
This may not be the best code in the world but it works!
And in light of trying to rewrite all code in my project with async and await I'll stick with this!
Public Class Form1
Private flag As Boolean = False
Dim Completed As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
flag = True
Await Task.Run(Sub()
Do
Loop Until Completed
End Sub)
If Completed = True Then
Label1.BackColor = Color.Red
End If
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Do
Thread.Sleep(250)
Loop Until flag
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As
RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Completed = True
End Sub
End Class
For all of the academics out there this code is more appropriate.
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Private Completed As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.WorkerSupportsCancellation = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles
Button1.Click
BackgroundWorker1.CancelAsync()
Await Task.Run(Sub()
Do
Loop Until Completed
End Sub)
If Completed = True Then
Label1.BackColor = Color.Red
End If
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Do
Thread.Sleep(250)
Loop Until BackgroundWorker1.CancellationPending
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Completed = True
End Sub
End Class
I'm trying to load a website, wait for it to fully load and then display a message box when it's loaded.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WebBrowser1.Navigate("https://www.google.com")
Do Until WebBrowser1.ReadyState = 4
Threading.Thread.Sleep(100)
Loop
MsgBox("Loaded")
End Sub
However, when I use this nothing happens, at all despite waiting about 30 seconds. If I remove everything to leave me with...
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WebBrowser1.Navigate("https://www.google.com")
End Sub
..., It loads fine.
The UI isn't going to load the page because you've tied up the UI thread in an infinite (or at least near infinite) loop.
Instead of waiting for the browser to load, asynchronously respond to the event of the browser loading. Something like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WebBrowser1.Navigate("https://www.google.com")
End Sub
Private Sub WebBrowser1_DocumentCompleted(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
MsgBox("Loaded")
End Sub
Any time you find yourself wanting to "pause" an application to wait for something to happen, it's highly likely that you need to subscribe to an event when that something happens.
If I'm seeing this right, its due to the fact you're making the main thread stop everytime you're running the check. Couldn't you just add an event handler on the DocumentCompleted event?
So basically I've got three listboxes containing items. I want the items to be deletable, so for this I've got a ContextMenuStrip with only one item : Delete. Though, I'd like the items to be deletable, too, via a press on the Delete key. So i've got my code, that you can see here :
Dim TempList As New List(Of String)
For Each Trigger In ListBoxTriggers.SelectedItems
TempList.Add(Trigger)
Next
For Each Trigger In TempList
ListBoxTriggers.Items.Remove(Trigger)
Next
It's a little longer because there is data related stuff but now this is the part concerning the removing from the ListBox. Now, for this I've been using
Private Sub ToolStripMenuItemTriggers_Click(sender As Object, e As EventArgs) Handles SupprimerToolStripMenuItemTriggers.Click
(supprimer means delete in French). But the thing is I'd like to process the
Private Sub ListBoxDescription_KeyDown(sender As Object, e As KeyEventArgs) Handles ListBoxDescription.KeyDown
in the same method. But I can't since e is not the same type... I of course can copy the same code in both handlers but that's not really... clean. I can, too, just create another method that I'll call in both ases like
Private Sub ListBoxDescription_KeyDown(sender As Object, e As KeyEventArgs) Handles ListBoxDescription.KeyDown
Delete()
End Sub
Private Sub ToolStripMenuItemTriggers_Click(sender As Object, e As EventArgs) Handles SupprimerToolStripMenuItemTriggers.Click
Delete()
End Sub
But I don't really like it neither... doesn't look like the most efficient solution...
Is there anything I can do for this ?
Thank you in advance
KeyEventArgs derives from EventArgs, so you can declare
Private Sub ListBoxDescription_KeyDown(sender As Object, e As EventArgs) Handles ListBoxDescription.KeyDown
and if you actually need e as KeyEventArgs then you can use
Dim kea = DirectCast(e, KeyEventArgs)
Also, if your delete method has a signature like
Sub DeleteThings(sender As Object, e As EventArgs)
then you can do
AddHandler ListBoxDescription.KeyDown, AddressOf DeleteThings
AddHandler ToolStripButton1.Click, AddressOf DeleteThings
Note that you do not need a Handles clause when using AddHandler.
You'll have to write the common event handler similar to this:
Private Sub CommonEventHandler(sender As Object, e As EventArgs) _
Handles ToolStripMenuItemTriggers.Click, ListBoxDescription.KeyDown
If sender Is ListBoxDescription Then
Dim kea = DirectCast(e, KeyEventArgs)
If kea.KeyData <> Keys.F2 Then Exit Sub
End If
'' Common code
''...
End Sub
Works fine, pretty hard to win elegance points with it however. You might as well move the Common code into a separate private method. The usual advice is to treat whomever is going to maintain your code some day as a homicidal maniac that knows where you live.
My code works but I am in doubt of what I did because i set CheckForIllegalCrossThreadCalls to false which I think would give some side-effects to my backgroundworker. Here is my sample code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub go_Click(sender As Object, e As EventArgs) Handles go.Click
Try
If BackgroundWorker1.IsBusy <> True Then
BackgroundWorker1.RunWorkerAsync()
resetevent.Set()
End If
Catch ex As Exception
End Try
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Do
Label1.Text = x
Label2.Text = Label1.Text
Label3.Text = Label2.Tex
Label4.Text = Label3.Text
Label5.Text = Label4.Text
x+=1
Loop While (x < 100)
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As System.Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Try
Catch ex As Exception
End Try
End Sub
Private Sub BackgroundWorker1_Completed(sender As System.Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Try
Catch ex As Exception
End Try
End Sub
Is there a way for me to set values to labels inside a backgroundworker without setting CheckForIllegalCrossThreadCalls to False? Because I have experienced some bugs with my program where the loop suddenly stops even if the counter limit has not been reached yet.
Create the method
Private Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Private Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
and call setLabelTxt in DoWork.
EDIT:
I will add the explanation a bit later with references as I am a bit busy right now.
I had your problem also and this worked for me.
EDIT:
"The way to safely access controls from worker threads is via delegation.
First you test the InvokeRequired property of the control, which will tell you whether or not you can safely access the control. InvokeRequired is one of the few members of the Control class that is thread-safe, so you can access it anywhere. If the property is True then an invocation is required to access the control because the current method is executing on a thread other than the one that owns the control's Handle.
The invocation is performed by calling the control's Invoke or BeginInvoke method. You create a delegate, which is an object that contains a reference to a method. It is good practice to make that a reference to the current method. You then pass that delegate to the Invoke or BeginInvoke method. That will essentially call the referenced method again, this time on the thread that owns the control's Handle."
Source: jmcilhinney post Accessing Controls from Worker Threads
http://www.vbforums.com/showthread.php?498387-Accessing-Controls-from-Worker-Threads
I can't explain better than him as I'm a noob also
During the ProgressChanged event, you can access the UI as it states in the MSDN documentation:
The ProgressChanged event handler executes on the thread that created the BackgroundWorker.
Thus, if you created the BackGroundWorker on the UI thread, you can update the UI, eliminating your need to CheckForIllegalCrossThreadCalls
Source:
MSDN backgroundworker
Just add this line of code on your Form.Load, it should be fine.
Control.CheckForIllegalCrossThreadCalls = False
Something like this is a single line and works.
Label1.Invoke(Sub() Label1.Text = "Meow")
I have read other posts about this but I still can't seem to get it to work right.
Whenever my BackgroundWorker begins to do work, my function API.CheckForUpdate causes the GUI to hang. I can't click on anything. It only freezes for half a second, but is enough to notice.
How can I fix this? Should I dive deeper into API.CheckForUpdate and run individual threads on particular statements, or can I just have an all-inclusive thread that handles this? API.CheckForUpdate does not reference anything in Form1.
Also, I presume Form1_Load is not the best place to put the RunWorkerAsync call. Where is a better spot?
'Declarations
Dim ApplicationUpdate As BackgroundWorker = New BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ApplicationUpdate.WorkerSupportsCancellation = True
ApplicationUpdate.WorkerReportsProgress = True
AddHandler ApplicationUpdate.DoWork, AddressOf ApplicationUpdate_DoWork
AddHandler ApplicationUpdate.ProgressChanged, AddressOf ApplicationUpdate_ProgressChanged
AddHandler ApplicationUpdate.RunWorkerCompleted, AddressOf ApplicationUpdate_RunWorkerCompleted
ApplicationUpdate.RunWorkerAsync()
End Sub
Private Sub ApplicationUpdate_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
'Check for an update (get the latest version)
Dim LatestVersion = API.CheckForUpdate
End Sub
Private Sub ApplicationUpdate_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
'Nothing here
End Sub
Private Sub ApplicationUpdate_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
'Work completed
MsgBox("Done")
End Sub
Its not a background worker Fix but if you don't mind walking around and not finding the answer, you can code like so:
Keep in mind when you first Start a Thread and you are coding in a Model you MUST pass (me) into the initial thread because of VB having a concept of "Default Form Instances". For every Form in the application's namespace, there will be a default instance created in the My namespace under the Forms property.
and that is just adding an additional parameter like so
----------------------/ Starting Main Thread /-----------------------------------
Private Sub FindCustomerLocation()
Dim Findcontractor_Thread As New Thread(AddressOf **FindContractor_ThreadExecute**)
Findcontractor_Thread.Priority = ThreadPriority.AboveNormal
Findcontractor_Thread.Start(me)
End Sub
------------------/ Running Thread /---------------
Private Sub **FindContractor_ThreadExecute**(beginform as *NameOfFormComingFrom*)
Dim threadControls(1) As Object
threadControls(0) = Me.XamDataGrid1
threadControls(1) = Me.WebBrowserMap
**FindContractor_WorkingThread**(threadControls,beginform) ' ANY UI Calls back to the Main UI Thread MUST be delegated and Invoked
End Sub
------------------/ How to Set UI Calls from a Thread / ---------------------
Delegate Sub **FindContractor_WorkingThread**(s As Integer,beginform as *NameOfFormComingFrom*)
Sub **FindContractor_WorkingThreadInvoke**(ByVal s As Integer,beginform as *NameOfFormComingFrom*)
If beginform.mouse.InvokeRequired Then
Dim d As New FindContractor_WorkingThread(AddressOf FindContractor_WorkingThreadInvoke)
beginform.Invoke(d, New Object() {s,beginform})
Else
beginform.Mouse.OverrideCursor = Cursors.Wait
'Do something...
beginform.Mouse.OverrideCursor = Nothing
End If
End Sub
Sources From Pakks Answer Tested!
Try starting the process outside the Load event. Create a Timer and start it on the Load event, and then handle the event for the tick:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
ApplicationUpdate.RunWorkerAsync()
End Sub