after using background worker also my application get stuck,,how i can resolve - vb.net

I am working on windows form application :
in my form am filling my datagrid view in some interval.so some time my application getting stuck..so i used back ground worker and timer
in back ground worker i am calling my function to fill the my data grid view.i set Timer Interval as 10000. in background worker i given code like this:
Private Sub BackgroundWorker1_DoWork
Call Fetch_Info()
End Sub
in Timer click event i given code like thise:
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
my Fetch_Info() function like this:
Dim cnt As Integer
Dim tbarcodedgv As String
Dim totaltbarcode As String
cnt = DGVall.RowCount
Dim tbar As String
Dim locTable As New DataTable
locTable.Columns.Add("carid", GetType(String))
If cnt > 0 Then
For i = 0 To cnt - 2
tbarcodedgv = DGVall.Rows(i).Cells(0).Value
locTable.Rows.Add(tbarcodedgv)
Next
End If
Dim flag As Boolean = False
Dim dcnt As Integer = DGVall.RowCount
Dim trid As Integer
Dim tbarcode As String
Dim keyloc As String
Dim cmd23 As New SqlCommand("IBS_fetchrequested", con.connect)
cmd23.CommandType = CommandType.StoredProcedure
cmd23.Parameters.Add("#tid", SqlDbType.Int).Value = tid
If cnt > 1 Then
Dim tvp1 As SqlParameter = cmd23.Parameters.AddWithValue("#Tbaroced", locTable)
tvp1.SqlDbType = SqlDbType.Structured
tvp1.TypeName = "dbo.TBarcode"
End If
dr = cmd23.ExecuteReader
While dr.Read
flag = False
tbarcode = dr("TBarcode")
If flag = False Then
If dr("keyloc") Is DBNull.Value Then
keyloc = ""
Else
keyloc = dr("keyloc")
End If
Dim row0 As String() = {tbarcode, keyloc, "", "Release"}
DGVall.Rows.Add(row0)
AxWindowsMediaPlayer1.URL = "C:\Beep.mp3"
End If
End While
dr.Close()
con.disconnect()

While your background worker runs in another thread than your GUI you are manipulating the Datagridview that's running in the GUI's thread. This should usually not work at all but it is probably the reason, why your GUI hangs while the BGW is running.
Try splitting the work: The time consuming fetching of data from the database is carried out in the Backgroundworker's DoWork event handler and you set the results as the e.Result value of the EventArgs variable in the DoWork function.
Then you handle the Backgroundworker's RunWorkerCompleted event and there you quickly update your datagridview with the results you set in the DoWork method. That way your GUI has nothing to do with the actual time consuming task and will only be affected by the quick update of your datagridview.
The code example for this is:
Public Class Form1
Private WithEvents LazyBGW As New System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'This code runs in the UI-Thread
LazyBGW.RunWorkerAsync()
End Sub
Private Sub LazyBGW_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles LazyBGW.DoWork
'This code runs in the BGW-Thread
Dim a As Integer = 0
For i = 1 To 5
a += 1
'I'm a lazy worker, so after this hard work I need to...
Threading.Thread.Sleep(1000) 'This locks up the BGW-Thread, not the UI-thread
Next
'Work is done, put results in the eventargs-variable for further processing
e.Result = a
End Sub
Private Sub LazyBGW_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LazyBGW.RunWorkerCompleted
'This code runs in the UI-Thread
Dim results As Integer = CInt(e.Result) 'e.Result contains whatever you put into it in the DoWork() method
MessageBox.Show("Finally the worker is done and our result is: " & results.ToString)
End Sub
End Class

Related

vb.NET You are not allowed to perform an operation across different threads

I am running code to read text from pdf files and than create a WordCloud. To inform the user the process is on going I add a BackgroundWorker to my form that shows an image saying Loading. I get an error for operating across different threads.
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
If ListBox1.SelectedIndex > -1 Then
For Each Item As Object In ListBox1.SelectedItems
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
Dim unused = MsgBox("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
Else
Dim unused = MsgBox("You have to choose an Archive to create the Word Cloud",, Title)
End If
Size = New Drawing.Size(1400, 800)
End Sub
I put above code in a Private Sub and called it from my BackgroundWorker. The error occured at this line: If ListBox1.SelectedIndex > -1 Then
After trying the suggested code I get the same error again:
Public Sub CreateMyCloud(ByVal sourcePDF As String)
Dim WordsFreqList As New List(Of WordsFrequencies)
For Each line As String In File.ReadLines(sourcePDF)
Dim splitText As String() = line.Split(","c)
If splitText IsNot Nothing AndAlso splitText.Length = 2 Then
Dim wordFrq As New WordsFrequencies
Dim freq As Integer
wordFrq.Word = splitText(0)
wordFrq.Frequency = If(Integer.TryParse(splitText(1), freq), freq, 0)
WordsFreqList.Add(wordFrq)
End If
Next
If WordsFreqList.Count > 0 Then
' Order the list based on the Frequency
WordsFreqList = WordsFreqList.OrderByDescending(Function(w) w.Frequency).ToList
' Add the sorted items to the listview
WordsFreqList.ForEach(Sub(wf)
error -> ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End If
Dim wc As WordCloudGen = New WordCloudGen(600, 400)
Dim i As Image = wc.Draw(WordsFreqList.Select(Function(wf) wf.Word).ToList, WordsFreqList.Select(Function(wf) wf.Frequency).ToList)
ResultPictureBox.Image = i
End Sub
Should look something more like:
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
If ListBox1.SelectedItems.Count > 0 Then
Dim data As New List(Of Object)
For Each Item As Object In ListBox1.SelectedItems
data.Add(Item)
Next
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
BackgroundWorker1.RunWorkerAsync(data)
Else
MessageBox.Show("You have to choose an Archive to create the Word Cloud",, Title)
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim data As List(Of Object) = DirectCast(e.Argument, List(Of Object))
For Each Item As Object In data
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
MessageBox.Show("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Size = New Drawing.Size(1400, 800)
End Sub
To fix the error in your second, edited post:
WordsFreqList.ForEach(Sub(wf)
ListView1.Invoke(Sub()
ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End Sub)
You cannot simply access controls outside of the current thread. You first need to call the Invoke method on the target control and then pass a delegate containing the instructions intended to modify the control outside of the current thread. See this article on MSDN on how to do this: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

VB Simple Threading using Delegates

I understand the concept of threading. I understand the concept of delegates but I am having trouble combining the two concepts. I followed a tutorial and I was able to make two counters start at the same time using multiple threads on my form. I was getting the cross threading error and I used the Me.CheckForIllegalCrossThreadCalls = False work around. I know my current method isnt ideal and I was wondering how I would use delegates to produce the same results. I have been at it all day and still cant seem to grasp the idea. How would I add delegates to the code below to allow two counters to work simultaneously on my form?
Public Class Form1
Dim i As Integer = 0
Dim i2 As Integer = 0
'declare two threads
'thread 1
Dim thread As System.Threading.Thread
'thread 2
Dim thread2 As System.Threading.Thread
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'replace countup() with, this will assign the countup method to thread 1
thread = New System.Threading.Thread(AddressOf countup)
thread.Start()
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
' countup2()
thread2 = New System.Threading.Thread(AddressOf countup2)
thread2.Start()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Label1.Text = i
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Label2.Text = i2
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
End Class
I would love to see the code using delegates but what I would really like is to have the understanding of whats going on.
Thanks guys
Not sure if this is exactly what you're looking for, but here's my best shot at it:
Module Module1
Dim i As Integer = 0
Dim i2 As Integer = 0
Public Delegate Sub counting()
Sub Main()
Dim del2 As counting = AddressOf countup2
Dim callback2 As IAsyncResult = del2.BeginInvoke(Nothing, Nothing)
Dim del1 As counting = AddressOf countup
Dim callback1 As IAsyncResult = del1.BeginInvoke(Nothing, Nothing)
del2.EndInvoke(callback2)
del1.EndInvoke(callback1)
Console.ReadLine()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Loop
Console.WriteLine("i = " & i)
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Loop
Console.WriteLine("i2 = " & i2)
End Sub
End Module
Sorry I have the first and second parts reversed and it's a console app instead of a form, but I figured the important part was to demonstrate delegates...
As a note, I'm not sure how familiar you are with delegates, but I included the EndInvoke to make sure the program wouldn't terminate prior to the delegates finishing their operations. They are used to return any values or exceptions from the method call as well as making the program wait. (In this case, since it's a sub there is no return value, so I didn't bother worrying about it)
One should use Control.Invoke to execute a specified delegate on the thread that owns the control's underlying window handle. Also, replace Me.Refresh() with Thread.Sleep(1) to ensure that other threads get some execution time.
Private Sub countup()
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.Label1.Text = i.ToString())
Thread.Sleep(1)
Next
End Sub
Here's an example.
' n=0 n=1
Private threads As Thread() = New Thread(2 - 1) {Nothing, Nothing}
Private Sub ButtonsClick(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click
Dim n As Integer = -1
If (sender Is Me.Button1) Then
n = 0
ElseIf (sender Is Me.Button2) Then
n = 1
End If
If (n <> -1) Then
If (Me.threads(n) Is Nothing) Then
'Start new thread.
Me.threads(n) = New System.Threading.Thread(Sub() Me.CountUp(n))
Me.threads(n).Start()
Else
'Abort thread.
Me.threads(n).Abort()
Me.threads(n) = Nothing
End If
End If
End Sub
Public Sub DisplayCount(n As Integer, text As String)
'Inside UI thread.
If (n = 0) Then
Me.Label1.Text = text
ElseIf (n = 1) Then
Me.Label2.Text = text
End If
End Sub
Private Sub CountUp(n As Integer)
'Inside worker thread.
Try
If ((n < 0) OrElse (n > 1)) Then
Throw New IndexOutOfRangeException()
End If
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.DisplayCount(n, i.ToString()))
Thread.Sleep(1)
Next
Catch ex As ThreadAbortException
Me.Invoke(Sub() Me.DisplayCount(n, "Cancelled"))
Thread.Sleep(1)
Catch ex As Exception
'TODO: Handle other exceptions.
End Try
End Sub
using Me.CheckForIllegalCrossThreadCalls = False is not the right approach.
Basically, Cross-thread operation not valid exception is raised when a control is being updated from a thread other than the thread it was created on.
Each control exposes a InvokeRequired property that allows it to be updated in a thread-safe manner.
Therefore the right way to update the label is to use code like -
Private Delegate Sub UpdateLabelDelegate(i As Integer)
Private Sub UpdateLabel(i As Integer)
If Label1.InvokeRequired Then
Dim del As New UpdateLabelDelegate(AddressOf UpdateLbl)
Label1.Invoke(del, New Object() {i})
'Me.Refresh()
Else
' this is UI thread
End If
End Sub
Private Sub UpdateLbl(i As Integer)
Label1.Text = i.ToString()
End Sub
Delegate.BeginInvoke will execute the method on a thread pool thread. Once the method returns, the thread is returned to the pool.
So basically instead of starting a new thread, you will asynchronously execute the method using Delegate.BeginInvoke

New Multi-threading not working

I'm trying to create a thread so when I click a button it creates a new PictureBox from a class, this is how far I've got but nothing comes up on the screen at all.
Form1 code:
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
pgClass.thread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private trd As Thread
Public Sub thread()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = False
trd.Start()
End Sub
Private Sub NewUIThread()
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
Application.DoEvents()
Threading.Thread.Sleep(100)
Next
counter += 1
End Sub
End Class
I have posted something similar before on here but it was different, the pictureBoxes were showing on the screen but I was trying to get them to move at the same time but they wouldn't move, they only moved one at a time. The question that I asked before was this Multi threading classes not working correctly
I made a few assumptions for this answer so it may not work for you out of the box but I think it will put you on the right track without using any Thread.Sleep calls because I personally don't like building intentional slows to my apps but that's a personal preference really.
So For my example I just used a bunch of textboxes because I didn't have any pictures handy to fiddle with. But basically to get it so that the user can still interact with the program while the moving is happening I used a background worker thread that is started by the user and once its started it moves the textboxes down the form until the user tells it to stop or it hits an arbitrary boundary that I made up. So in theory the start would be the space bar in your app and my stop would be adding another control to the collection. For your stuff you will want to lock the collection before you add anything and while you are updating the positions but that is up to your discretion.
So the meat and potatoes:
in the designer of the form I had three buttons, btnGo, btnStop and btnReset. The code below handles the click event on those buttons so you will need to create those before this will work.
Public Class Move_Test
'Flag to tell the program whether to continue or to stop the textboxes where they are at that moment.
Private blnStop As Boolean = False
'Worker to do all the calculations in the background
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker
'Controls to be moved.
Private lstTextBoxes As List(Of TextBox)
'Dictionary to hold the y positions of the textboxes.
Private dtnPositions As Dictionary(Of Integer, Integer)
Public Sub New()
' Default code. Must be present for VB.NET forms when overwriting the default constructor.
InitializeComponent()
' Here I instantiate all the pieces. The background worker to do the adjustments to the position collection, the list of textboxes to be placed and moved around the form
' and the dictionary of positions to be used by the background worker thread and UI thread to move the textboxes(because in VB.NET you can not adjust controls created on the UI thread from a background thread.
bgWorker = New System.ComponentModel.BackgroundWorker()
Me.lstTextBoxes = New List(Of TextBox)
Me.dtnPositions = New Dictionary(Of Integer, Integer)
For i As Integer = 0 To 10
Dim t As New TextBox()
t.Name = "txt" & i
t.Text = "Textbox " & i
'I used the tag to hold the ID of the textbox that coorelated to the correct position in the dictionary,
' technically you could use the same position for all of them for this example but if you want to make the things move at different speeds
' you will need to keep track of each individually and this would allow you to do it.
t.Tag = i
dtnPositions.Add(i, 10)
'Dynamically position the controls on the form, I used 9 textboxes so i spaced them evenly across the form(divide by 10 to account for the width of the 9th text box).
t.Location = New System.Drawing.Point(((Me.Size.Width / 10) * i) + 10, dtnPositions(i))
Me.lstTextBoxes.Add(t)
Next
'This just adds the controls to the form dynamically
For Each r In Me.lstTextBoxes
Me.Controls.Add(r)
Next
End Sub
Private Sub Move_Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'Don't need to do anything here. Placeholder
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Try
If Not bgWorker.IsBusy Then
'User starts the movement.
bgWorker.RunWorkerAsync()
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
Try
'Reset the positions and everything else on the form for the next time through
' I don't set the blnStop value to true in here because it looked cooler to keep reseting the textboxes
' and have them jump to the top of the form and keep scrolling on their own...
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, 10)
Next
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) = 10
Next
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub bgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'This is where we do all the work.
' For this test app all its doing is scrolling through each value in the dictionary and incrementing the value
' You could make the dictionary hold a custom class and have them throttle themselves using variables on the class(or maybe travel at an angle?)
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) += 1
Next
Catch ex As Exception
blnStop = True
End Try
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Try
'Once the background worker is done updating the positions this function scrolls through the textboxes and assigns them their new positions.
' We have to do it in this event because we don't have access to the textboxes on the backgroun thread.
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, dtnPositions(CInt(r.Tag)))
Next
'use linq to find any textboxes whose position is beyond the threshhold that signifies they are down far enough.
' I chose the number 100 arbitrarily but it could really be anything.
Dim temp = From r In Me.lstTextBoxes Where r.Location.Y > (Me.Size.Height - 100)
'If we found any textboxes beyond our threshold then we set the top boolean
If temp IsNot Nothing AndAlso temp.Count > 0 Then
Me.blnStop = True
End If
'If we don't want to stop yet we fire off the background worker again and let the code go otherwise we set the stop boolean to false without firing the background worker
' so we will be all set to reset and go again if the user clicks those buttons.
If Not Me.blnStop Then
bgWorker.RunWorkerAsync()
Else
Me.blnStop = False
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Try
'The user clicked the stop button so we set the boolean and let the bgWorker_RunWorkerCompleted handle the rest.
Me.blnStop = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
End Class
Theres a lot of code there but a lot of it is comments and I tried to be as clear as possible so they are probably a little long winded. But you should be able to plop that code on a new form and it would work without any changes. I had the form size quite large (1166 x 633). So I think that's when it works best but any size should work(smaller forms will just be more cluttered).
Let me know if this doesn't work for your application.
This is a problem that is well suited to async/await. Await allows you to pause your code to handle other events for a specific period of time..
Private Async Function Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) As Task Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
await pgClass.NewUIThread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private Async Function NewUIThread() As Task
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
await Task.Delay(100) 'The await state machine pauses your code here in a similar way to application.doevents() until the sleep has completed.
Next
counter += 1
End Sub
End Class

VB.net ListView Adding Items Multiple Times

I am having an issue where I get multiple entries in my ListView for the same item if I run my action more than once.
I am creating a simple network scanner/hostname grabber that will add the items to the listview as they come back alive to my ping test.
When I run it the first time it runs fine and creates one entry as it should.
When I run it subsequent times it creates the item as many times as I have ran the code ex. 3rd time hitting start it creates each entry 3 times when it should just create the entry once.
Here is my go button code:
Private Sub Go_Click(sender As Object, e As EventArgs) Handles Go.Click
Dim verifyIP
ListView1.Items.Clear()
chkDone = 0
verifyIP = ipChk(ipAdd.Text)
If verifyIP = 1 Then
ipAddy = Split(ipAdd.Text, ".")
pingTest1.WorkerReportsProgress = True
pingTest1.WorkerSupportsCancellation = False
AddHandler pingTest1.ProgressChanged, AddressOf pingTest1_ProgressChanged
pingTest1.RunWorkerAsync()
pingTest2.WorkerReportsProgress = True
pingTest2.WorkerSupportsCancellation = False
AddHandler pingTest2.ProgressChanged, AddressOf pingTest2_ProgressChanged
pingTest2.RunWorkerAsync()
pingTest3.WorkerReportsProgress = True
pingTest3.WorkerSupportsCancellation = False
AddHandler pingTest3.ProgressChanged, AddressOf pingTest3_ProgressChanged
pingTest3.RunWorkerAsync()
pingTest4.WorkerReportsProgress = True
pingTest4.WorkerSupportsCancellation = False
AddHandler pingTest4.ProgressChanged, AddressOf pingTest4_ProgressChanged
pingTest4.RunWorkerAsync()
While chkDone < 4
wait(25)
End While
Else
MsgBox("IP Invalid")
End If
MsgBox("Done")
End Sub
Here is the code from one of the background workers I am using:
Private WithEvents pingTest1 As BackgroundWorker = New BackgroundWorker
Private Sub pingTest1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles pingTest1.DoWork
Try
Dim hostCheck
pingResult1 = 0
pingTestDone1 = 0
tryIP1 = ipAddy(0) & "." & ipAddy(1) & "." & ipAddy(2) & ".1"
If My.Computer.Network.Ping(tryIP1) = True Then
'Dim pingsender As New Net.NetworkInformation.Ping
'If pingsender.Send(tryIP).Status = Net.NetworkInformation.IPStatus.Success Then
Try
'Dim host As System.Net.IPHostEntry
hostCheck = ""
'host = System.Net.Dns.GetHostByAddress(tryIP3)
'MsgBox(host.HostName)
'host3 = host.HostName
'hostCheck = System.Net.Dns.GetHostEntry(tryIP3).HostName
hostCheck = System.Net.Dns.GetHostByAddress(tryIP1)
'get the hostname property
hostCheck = hostCheck.HostName
pingTest1.ReportProgress("1", hostCheck)
Catch f As Exception
'MsgBox("Error: " & f.Message)
pingTest1.ReportProgress("1", "No Hostname Found")
End Try
Else
pingResult1 = 2
End If
Catch d As Exception
MsgBox("There was an error trying to ping the IP Address: " & d.Message)
End Try
End Sub
Private Sub pingTest1_ProgressChanged(e.ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
MsgBox("Hey")
Dim str(2) As String
Dim itm As ListViewItem
str(0) = tryIP1 & " Is Alive!!!"
str(1) = e.UserState
itm = New ListViewItem(str)
ListView1.Items.Add(itm)
str(0) = ""
str(1) = ""
itm = Nothing
End Sub
Private Sub pingTest1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles pingTest1.RunWorkerCompleted
chkDone = chkDone + 1
End Sub
I added the Hey box and sure enough the ProgressChanged event gets triggered the amount of times I have hit the Go button. Is it something I have coded incorrectly?
It's most likely because you're adding, but not removing your handlers for the progress changed, so you're handling the event multiple times.
Try adding your Progress Changed Event Handlers when you're instantiating your Background workers, rather than every time you click your button. This way they will only handled once.

VB.net Cross-thread operation not valid [duplicate]

This question already has answers here:
Crossthread operation not valid... - VB.NET
(8 answers)
Closed 8 years ago.
I am trying my best to figure out how to go about this error i am reciving:
Cross-thread operation not valid: Control 'ListView1' accessed from a thread other than the thread it was created on.
I have a backgroundworker thats extracting cells from an excel worksheet and placing them into the listview.
Upon form load i do this:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Call createListView()
End Sub
Private Sub createListView()
ListView1.View = View.Details
ListView1.GridLines = True
ListView1.FullRowSelect = True
ListView1.HideSelection = False
ListView1.MultiSelect = False
ListView1.Columns.Add("Column Name", 150)
ListView1.Columns.Add("Column Number", 150)
End Sub
Then i call the backgroundworker after the user selects a file:
If openFileDialog1.ShowDialog() = DialogResult.OK Then
stFilePathAndName = openFileDialog1.FileName
ProgressBar1.Style = ProgressBarStyle.Marquee
BGWxml2excel = New System.ComponentModel.BackgroundWorker
BGWxml2excel.WorkerReportsProgress = True
BGWxml2excel.WorkerSupportsCancellation = True
BGWxml2excel.RunWorkerAsync()
End If
Then i process with getting the excel column count and values so that i can populate the listview with it:
Private Sub BGWxml2excel_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BGWxml2excel.DoWork
Call xml2Excel(stFilePathAndName)
End Sub
Private Sub xml2Excel(ByRef theDirOfFile As String)
Dim xlsApp As Excel.Application
Dim xlsWB As Excel.Workbook
Dim xlsSheet As Excel.Worksheet
Dim columnCount As Integer = 0
xlsApp = New Excel.Application
xlsApp.Visible = False
xlsApp.DisplayAlerts = False
xlsWB = xlsApp.Workbooks.OpenXML(Filename:=theDirOfFile, LoadOption:=XlXmlLoadOption.xlXmlLoadImportToList)
xlsSheet = xlsWB.Worksheets(1)
xlsSheet.Select()
columnCount = xlsSheet.UsedRange.Columns.Count
Dim lvi As New ListViewItem
Dim x As Integer = 1
Do Until x = columnCount + 1
lvi.Text = xlsSheet.Cells(1, x).value
lvi.SubItems.Add(x)
ListView1.Items.Add(lvi)
x = x + 1
Loop
'xlsSheet.SaveAs("c:\_tempExcelFile.xlsx", FileFormat:=51, CreateBackup:=False)
xlsWB.Close()
xlsApp.Quit()
End Sub
The error is on this line:
ListView1.Items.Add(lvi)
What can i do in order to correct this odd proglem?
Thanks!
David
The problem is that only the UI thread can update the UI. Because you are adding an item to the ListView from your worker, you are getting this exception.
To fix this, you could store items that you want to add to the listview in a shared variable (one to which both the UI thread and your worker have access), put a timer on your form (so the UI threads hits the tick event handler) and add the items within the tick handler.
Watered-down example (in C#, because it's a lot faster for me :-) ):
private List<ListViewItem> _itemsToBeAdded = new List<ListViewItem>();
private readonly object _lockObject = new object();
// worker method:
private void xml2Excel(string input)
{
// do some processing...
ListViewItem lvi = new ListViewItem();
// set up lvi
lock(_lockObject)
{
_itemsToBeAdded.Add(lvi);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
lock(_lockObject)
{
foreach(var item in _itemsToBeAdded)
{
ListView1.Add(item);
}
}
}
Only the UI thread can access the UI. Your background worker is on another thread. You'll need to use .InvokeRequired/.Invoke to get back to the UI thread.
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
The event handler's for the background worker will be raised on the main thread; you can safely alter the UI there. But actually in the background thread, you'll have to invoke. Like this:
Delegate Sub SetTextCallback([text] As String)
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub