Status for threading - vb.net

I am using a multi thread concept in my application.I am using below code
Dim threadHistory As Thread = Nothing
For Each dRow As DataRow In sqlDS.Tables(0).Rows
GetPropertyBidHistory(dRow("ID"))
threadHistory = New Threading.Thread(AddressOf GetRowHistory)
threadHistory.Name = "Row" + dRow("ID")
threadHistory.Start(dRow("ID"))
threadHistory.Join()
Next
Public Sub GetRowHistory(ByVal ID As String)
'1 min code from web srvice
End Sub
If i have 10 Id's , how can i know whether all 10 threads were completed or not.

You're starting and joining the thread one after the other. That is, perhaps, not your intent. If you keep it that way ou create a single thread, wait for it to finish and only then proceed to the next element.
I'd try the following: for each thread add it to a list or array and after the For Each/Next Statement You can Join them all with the property, i guess, JoinAll()
Dim List( Of Thread) allThreads = new List
Dim threadHistory As Thread = Nothing
For Each dRow As DataRow In sqlDS.Tables(0).Rows
GetPropertyBidHistory(dRow("ID"))
threadHistory = New Threading.Thread(AddressOf GetRowHistory)
threadHistory.Name = "Row" + dRow("ID")
allThreads.Add(threadHistory)
threadHistory.Start(dRow("ID"))
Next
Thread.JoinAll(allThreads) 'Blocks until all the threads finish

Related

Ping multiple device names (hostname) on the Network

A DataGridView displays hostnames at Column index 0, computer / printer names on the network.
pc1
pc2
print3
pc5
print
....
There are more than 500 such names.
I know how to ping them:
For i = 0 To DataGridView1.Rows.Count - 1
Try
If My.Computer.Network.Ping(DataGridView1.Item(0, i).Value) = True Then
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Lime
Else
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End If
Catch ex As Exception
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End Try
Next
The problem is that the Ping takes a very long time and the application freezes.
How can you speed up this procedure?
And let's say if the node is not available, then simply remove it from the list.
An example to Ping multiple addresses at the same time, using the async version of provided by the Ping class, Ping.SendPingAsync().
This version is await-able, not the same as the Ping.SendAsync() method, still asynchronous but event-driven.
Since you're using a DataGridView to both store the IpAddress/HostName and to present the PingReply results, you need to determine a way to match the Ping result to correct Cell of the DataGridView from which the Ip/Host address was taken.
Here, I'm passing to the method the Row's Index, so when the Ping result comes back, asynchronously, we can match the response to a specific Cell in the DataGridView.
To make the initialization method more generic, I'm passing also the index of the Column where the Ip/Host address is stored and the index of the Column that will show the result (you could also just pass all indexes, not a DataGridView Control reference to the method and handle the results in a different way).
A loop extracts the addresses from the the DataGridView and creates a List(Of Task), adding a PingAsync() Task for each address found.
When the collection is completed, the List(Of Task) is passed to the Task.WhenAll() method, which is then awaited.
This method starts all the Task in the list and returns when all Task have a result.
► Note that the Ping procedure sets a TimeOut, to 5000ms here, so all the Tasks will return before or within that interval, successful or not.
You can then decide if you want to reschedule the failed Pings or not.
The UI update is handled using a Progress delegate. It's just a method (Action delegate) that is called when the Ping procedure has a result to show.
It can also be used when the method that updates the UI runs in a different Thread: the Report() method will call the Progress object delegate in the Thread that created the delegate: the UI Thread, here (in the example, we're not actually ever leaving it, though).
This is how it works:
Assume you're starting the ping sequence from Button.Click event handler.
Note that the handler is declared async.
Private Async Sub btnMassPing_Click(sender As Object, e As EventArgs) Handles btnMassPing.Click
Await MassPing(DataGridView1, 1, 2)
End Sub
Initialization method and IProgress<T> report handler:
Imports System.Drawing
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Threading.Tasks
Private Async Function MassPing(dgv As DataGridView, statusColumn As Integer, addressColumn As Integer) As Task
Dim obj = New Object()
Dim tasks = New List(Of Task)()
Dim progress = New Progress(Of (sequence As Integer, reply As Object))(
Sub(report)
SyncLock obj
Dim status = IPStatus.Unknown
If TypeOf report.reply Is PingReply Then
status = DirectCast(report.reply, PingReply).Status
ElseIf TypeOf report.reply Is SocketError Then
Dim socErr = DirectCast(report.reply, SocketError)
status = If(socErr = SocketError.HostNotFound,
IPStatus.DestinationHostUnreachable,
IPStatus.Unknown)
End If
Dim color As Color = If(status = IPStatus.Success, Color.Green, Color.Red)
Dim cell = dgv(statusColumn, report.sequence)
cell.Style.BackColor = color
cell.Value = If(status = IPStatus.Success, "Online", status.ToString())
End SyncLock
End Sub)
For row As Integer = 0 To dgv.Rows.Count - 1
If row = dgv.NewRowIndex Then Continue For
Dim ipAddr = dgv(addressColumn, row).Value.ToString()
tasks.Add(PingAsync(ipAddr, 5000, row, progress))
Next
Try
Await Task.WhenAll(tasks)
Catch ex As Exception
' Log / report the exception
Console.WriteLine(ex.Message)
End Try
End Function
PingAsync worker method:
Private Async Function PingAsync(ipAddress As String, timeOut As Integer, sequence As Integer, progress As IProgress(Of (seq As Integer, reply As Object))) As Task
Dim buffer As Byte() = New Byte(32) {}
Dim ping = New Ping()
Try
Dim options = New PingOptions(64, True)
Dim reply = Await ping.SendPingAsync(ipAddress, timeOut, buffer, options)
progress.Report((sequence, reply))
Catch pex As PingException
If TypeOf pex.InnerException Is SocketException Then
Dim socEx = DirectCast(pex.InnerException, SocketException)
progress.Report((sequence, socEx.SocketErrorCode))
End If
Finally
ping.Dispose()
End Try
End Function

Datarow ends up with wrong or lost data

I'm scraping twitter tweets, I launch multiple backgroundworkers and they do the following:
For x as Integer = 0 to 5
Dim BGW As New BackgroundWorker
AddHandler BGW.DoWork, AddressOf TweetGrab
BGW.RunWorkerAsync(tweeturl)
Next
Public TemporaryRows As New List(Of DataRow)
Private Sub TweetGrab(tweeturl as String)
'some html stuff here
Dim ImageUrl as String = twitterImage.Attributes("src").Value
Dim ThumbnailUrl As String = ImageUrl & ":small"
Dim DataRowTemporary As DataRow = DataTable1.NewRow()
DataRowTemporary("ImageUrl") = ImageUrl
DataRowTemporary("ThumbnailUrl") = ThumbnailUrl
DataRowTemporary("Checked") = False
'I detect the error even here
TemporaryRows.Add(DataRowTemporary)
End Sub
Later on, I do stuff with the TemporaryRows. I loop over the rows and check if they meet some conditions.
The problem is that DataRowTemporary("Checked") ends being DBNull and DataRowTemporary("ThumbnailUrl") is completely different than ImageUrl even though I specified Dim ThumbnailUrl As String = ImageUrl & ":small"
This happens in about 2/10 cases. I would guess it has something to do with background threads but I don't have any ideas how to solve it. I can reedit the fields after the error occurs, but I would like to prevent the error from occurring in the first place.
The problem was changing the collection when its being accessed by other threads (in parallel).
You can not do add/remove in parallel, the collection must be locked in order to not get strange errors.
SyncLock TemporaryRows
TemporaryRows 'Add/Remove
End SyncLock

How to pause loop while multithreading is alive

I have 3 threads that are called inside a loop.
For i As Integer = 0 To DG.Rows.Count - 1
Dim thread1 = New System.Threading.Thread(AddressOf processData)
Dim thread2 = New System.Threading.Thread(AddressOf processData2)
Dim thread3 = New System.Threading.Thread(AddressOf processData3)
If Not thread1.IsAlive Then
x1 = i
thread1.Start()
ElseIf Not thread2.IsAlive Then
x2 = i
thread2.Start()
ElseIf Not thread3.IsAlive Then
x3 = i
thread3.Start()
End If
Next
How do I pause the loop while all threads are alive?
What I want is, if one of the threads finishes then continue the loop and get the (i), then pause the loop again if there are no available threads. Because sometimes DG.Rows items are more than 3.
Let the framework handle this for you: use the ThreadPool.
First, create an array to hold thread status for each item:
Dim doneEvents(DG.Rows.Count) As ManualResetEvent
Like the x1,x2,x3 variables, this needs to be accessible from both your main thread and the processData method.
Then modify your processData method to accept an Object argument at the beginning and set a ResetEvent at the end:
Public Sub processData(ByVal data As Object)
Dim x As Integer = CInt(data)
'...
'Existing code here
doneEvents(x).Set()
End Sub
Now you can just queue them all up like this:
For i As Integer = 0 To DG.Rows.Count - 1
ThreadPool.QueueUserWorkItem(processData, i)
Next
WaitHandle.WaitAll(doneEvents)
Console.WriteLine("All data is processed.")
Though I suspect you should also pass the data from your grid for each row to the processData method.
You can also use the newer Async/Await keywords, but I'll have a hard time writing a sample for this without knowing something of the contents of processData.
I think you want to do something like this. Don't pause, just launch a thread per loop iteration.
For i As Integer = 0 To DG.Rows.Count - 1
Dim thread1 = New System.Threading.Thread(AddressOf processData)
thread1.Start(i)
Next
But in any case, I don't think you want to call new System.Threading.Thread in each loop. Those should be moved outside the For loop.
It could be that you use TPL's Parallel methods and write your code like this:
Parallel.For( _
0, _
DG.Rows.Count, _
New ParallelOptions() With {.MaxDegreeOfParallelism = 3}, _
Sub(i) processData(i))
I don't understand why you have processData, processData2, and processData3 though.

Looping Through List Inside Parallel.Foreach

I would like to iterate through a list inside of a Parallel.ForEach loop, but the list will be a shared resource that I have to take turns accessing, which I think would defeat the purpose of the parallelism. Access to the list is read-only, so I was wondering if there was a way to make copies of the list, and allow each thread to pick one if it is not in use by one of the other threads.
Dim collCopy1 As List(Of InnerType) = innerCollection.ToList()
Dim collCopy2 As List(Of InnerType) = innerCollection.ToList()
Dim collCopy3 As List(Of InnerType) = innerCollection.ToList()
Dim collCopy4 As List(Of InnerType) = innerCollection.ToList()
Dim Lock As New Object
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Task.Factory.StartNew(Sub()
Parallel.ForEach(outerCollection,
ParallelOpts,
Sub(outerItem As OuterType)
'Pick from collCopy1, collCopy2, collCopy3, collCopy4 here, assign to innerList, and SyncLock it
For Each innerItem As InnerType In innerList
'Do some stuff
Next
End Sub)
End Sub)

VBNet Error: Collection was modified; enumeration operation may not execute

This is what happened: on the form load of my application i created a background worker to bind collection (records from database filled in the dataset) on my control. but the problem is when the i updated the records on the database it throws an error if i run this procedure again.
If xControl.InvokeRequired Then
Dim MyDelegate As New InitializeDataBinding_Delegate(AddressOf InitializeDataBinding)
Invoke(MyDelegate, New Object() {xControl, xQuery, xPrimaryKey}) ' ERROR HERE SAYING: Collection was modified; enumeration operation may not execute.
Else
Using ds As DataSet = New DataSet()
Using dbAdapter As MySqlDataAdapter = New MySqlDataAdapter(xQuery, ConnectionClass.ConnectionString)
dbAdapter.Fill(ds)
End Using
Dim dvm As DataViewManager = New DataViewManager(ds)
Dim iDataList As DataView = dvm.CreateDataView(ds.Tables(0))
For Each iBind As Binding In xControl.DataBindings
xControl.DataBindings.Remove(iBind)
Next
xControl.DataBindings.Add("EditValue", iDataList, xPrimaryKey)
xControl.Properties.DataSource = iDataList
xControl.EditValue = Nothing
txtStatus.Text = "Ready"
End Using
End If
You have to avoid updation in collection while iterating it using For Each. Use simple For loop instead of For Each.
You can't use a For Each loop to remove items from a diction or KeyValuePair, but you can use a regular for loop, get the key and use the key to remove the item from the list.
For i As Integer = 0 To oDictionary.Count - 1
Dim sKey = m_oDictionary.ElementAt(i).Key
m_oDictionary.Remove(sKey)
Next
Solved by adding:
xControl.DataBindings.Clear()
If xControl.InvokeRequired Then
Dim MyDelegate As New InitializeDataBinding_Delegate(AddressOf InitializeDataBinding)
Invoke(MyDelegate, New Object() {xControl, xQuery, xPrimaryKey}) ' ERROR HERE SAYING: Collection was modified; enumeration operation may not execute.
Else
Using ds As DataSet = New DataSet()
Using dbAdapter As MySqlDataAdapter = New MySqlDataAdapter(xQuery, ConnectionClass.ConnectionString)
dbAdapter.Fill(ds)
End Using
xControl.DataBindings.Clear() 'HERE
Dim dvm As DataViewManager = New DataViewManager(ds)
Dim iDataList As DataView = dvm.CreateDataView(ds.Tables(0))
For Each iBind As Binding In xControl.DataBindings
xControl.DataBindings.Remove(iBind)
Next
xControl.DataBindings.Add("EditValue", iDataList, xPrimaryKey)
xControl.Properties.DataSource = iDataList
xControl.EditValue = Nothing
txtStatus.Text = "Ready"
End Using
End If
Dim index As Integer = 0
opnieuw:
For Each F In openbestand.file
If F.Contains("~$") Then
openbestand.file.Remove(openbestand.file(index))
openbestand.path.Remove(openbestand.path(index))
GoTo opnieuw
Else
index = (index + 1)
End If
Next
If you are going to use a loop, you need to ensure that the index is less than the Count, since the count decreases every time a key element is removed:
Dim i As Integer
For i = 0 To dictionary1.Count - 1
If i <= dictionary1.Count - 1 Then
Dim sKey = dictionary1.ElementAt(i).Key
dictionary1.Remove(sKey)
End If
Next