Starting same thread from a foreach loop not working - vb.net

Is there a way to start same Thread form foreach loop
Sub
For Each lvItem As ListViewItem In _ListView.SelectedItems
tThread = New Thread(AddressOf Me.myFunction())
tThread .Start()
Next
End Sub
Sub myFunction()
//Code
End Sub
In my case, when i select one item from list it is working fine...but when i select more than one files it odes not work.

I want to select multiple files (which are file paths) from ListView and convert them in mp3 files but with above solution. it converts first selected file successfully but then stops.
Creating a separate thread for each file is sub-optimal. In some cases, this can actually result in worse performance than doing them all sequentially, because your system will spend too much time switching back and forth between different threads. Instead, you want to choose a small number of threads and queue your items for time by those threads.
There are a lot of ways to implement this:
You can write custom code that bases the number of threads on the number of physical processor cores (greater of 2 or the number of cores - 1 is common). This is a lot of extra work, and is error prone.
You can use the built-in in ThreadPool.QueueUserWorkItem(). This is great, but it can be tricky to track progress of your items.
You can use the Task Parallel Library. This requires .Net 4, but it's probably your best option by far. The extra work upfront learning the concepts will also be a huge payoff, and it sounds like Microsoft is basing some of the more important features in .Net 5 around the Task concept.

I'm guessing that instead of having one variable tThread storing the thread, you probably meant to store all the threads, so instead have a List of them. So something like:
Sub
Dim threads as New List(Of Thread)
For Each lvItem As ListViewItem In _ListView.SelectedItems
tThread = New Thread(AddressOf Me.myFunction())
tThread .Start()
threads.Add(tThread)
Next
End Sub
Otherwise, if there's another issue, please supply more details.

Are you sure the items are selected? I tried the following with a few items in the listview and it worked as expected.
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
For Each lvItem As ListViewItem In ListView1.SelectedItems
Dim t As Threading.Thread = New Threading.Thread(AddressOf myFunction)
t.Start(lvItem)
Next
End Sub
Private Sub myFunction(ByVal lvi As Object)
Dim lvItem As ListViewItem = CType(lvi, ListViewItem)
Debug.WriteLine(lvItem.Text)
End Sub

Related

How to properly interact with UI in threading in VB.NET

I'm new to VB.NET threading
As for simple testing I tried the following, which I need to smoothly fill a listbox with values.
But it does not work as I expect, it hangs the interface. Please let me know what I'm doing wrong here.
Thank you.
Imports System.Threading
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Thr As Threading.Thread
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
'Thr.SetApartmentState(ApartmentState.STA)
Thr.IsBackground = True
Thr.Start()
End Sub
Private Delegate Sub DoStuffDelegate()
Private Sub tprocess()
Dim i As Integer
For i = 0 To 20000
If Me.InvokeRequired Then
Me.Invoke(New DoStuffDelegate(AddressOf tprocess))
Else
ListBox1.Items.Add(i)
End If
Next
End Sub
End Class
When you write code to create a thread then you always have to worry about the kind of bugs that threading can cause. They are very hard to diagnose, the only decent way to address them is to know they exist and to write the code carefully so you know how to avoid them.
The most common threading bugs are threading races, deadlock and firehose bugs. You have the 1st and the 3rd bug in your code. You are complaining about the 3rd. Very quickly: the threading race bug is using Me.InvokeRequired. You have no guarantee that it is still true when the Me.Invoke() statement executes. This goes wrong when the user closes the window while your thread is still running. When you try to fix this problem you'll get to see what the 2nd bug looks like. But you are not there yet.
The firehose bug is the Me.Invoke() call. Very fast, takes less than a microsecond of work for the worker thread, you do it 20000 times at a very high rate. It is however another thread that must actually do the work of adding the item, your UI thread. That is not fast, it not only has to add the item but it also needs to repaint the control. Many microseconds.
While this goes on, your UI thread is burning 100% core, trying to keep up with the relentless rate of invoke requests. Working as hard as it can to add items to the listbox. Something has to give, while it is doing this it is not taking care of the lower priority jobs it has to do. Painting and responding to user input. In effect, your UI looks completely frozen. You can't see it paint anymore and trying to, say, close the window doesn't work. It isn't actually dead, it is hard at work.
Takes a while, probably a few handful of seconds, give or take. Until the worker thread finishes its for() loop and stops slamming the UI thread with invoke requests. And everything turns back to normal.
A firehose bug like this is pretty fundamental. The only way to fix it is to call Invoke() less often or at a lower rate. Note how putting Thread.Sleep(50) after the Invoke() call instantly fixes it. But of course that slows down your worker thread a lot. You call Invoke() less often by using AddRange() instead of Add(), adding (say) 1000 items at a time. Which is the proper fix but now it becomes fairly pointless to still try to update the listbox from the worker thread. Might as well do it with a single AddRange() call. The quickest way.
Try changing:
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
to this:
Thr = New Threading.Thread(AddressOf tprocess)
ThreadStart will start that thread immediately
I tried the following way. It's almost easy for me to handle. Backgroudworker manages this situation perfectly well.
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim i As Integer
For i = 1 To 20000
BackgroundWorker1.ReportProgress((i / 20000) * 100, i)
Threading.Thread.Sleep(1)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
ListBox1.Items.Add(e.UserState)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox("Complete")
End Sub

Multithreading in vb.net to simulate task

I have a program that is doing one task.
For Example i have one list box containing some links.
And on the other hand my program is opening them one by one but i want it to be done faster
i have used for-each loop for that purpose.
All what i want to do is i wanna give every 2 or 3 link to a different thread or if there is any other solution to make it Faster Kindly tell me.
This is a small piece of code from my program.
For value As Integer = 1 To TextBox1.Text
If (value = TextBox1.Text) Then
Exit For
End If
Dim page As String = "-p-" & value
Extractor.ScrapLinks(txturl.Text + page, lstbox)
lbllinks.Text = lstbox.Items.Count
Next
I don't know what Extractor.ScrapLinks actually do, but it seems that you need to access UI thread and you cannot create multiple UI threads so eventually you will process them sequentially
What you can do to improve the solution is to read the data you want from the UI controls and then process that data on a separate thread, after completion you can fill the results into the UI by invoking some method on the UI thread as shown below
Delegate Sub PerformOperationDel(value As Integer)
Sub PerformOperation(value As Integer)
Dim page As String = "-p-" & value
Extractor.ScrapLinks(txturl.Text + page, lstbox)
lbllinks.Text = lstbox.Items.Count
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For value As Integer = 1 To CInt(TextBox1.Text) - 1
lstbox.BeginInvoke(New PerformOperationDel(AddressOf PerformOperation))
Next
End Sub
You can use backgroundworkers but notice that you cannot access UI controls in the DoWork but you can access them on work completed (Refer to: Background worker proper way to access UI)
Best of luck

Datagridview linked to datatable not getting updated

This is a part of simultaneous url download program that i'm trying to make. It has the url list saved in a datatable named tbl and it is bound to a datagridview named dgvUrls. Evrytime it encounters a dead url, it removes it from the datatable.
I've reproduced the error using the code below. The Button3_Click adds 100 rows to the datatable, makes it as the datasource for datagridview. The q() removes the rows one at a time by removing the 1st row. The prob is that the datagridview don't reflect the changes made in the datatable
Dim tbl = New DataTable
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
'Add 100 urls, for simplicity i'm adding only integers
tbl.Columns.Add("Urls")
For i = 1 To 100
tbl.Rows.Add(i)
Next
'bind to datagridview so that the end user can see the urls being download/removed from the list
dgvUrls.DataSource = tbl
'start multithread download , for simplicited (of this question) we have only one
Dim t As Thread = New Thread(AddressOf Download)
t.Start()
t.Join()
dgvUrls.Refresh()
End Sub
Private Sub download()
'for simplicity, the 1st 80 urls were dead!
For i = 1 To 80
'we remove the dead urls
tbl.Rows.RemoveAt(0)
Next
In general, it is a good thing to Refresh the DataGridView, mainly if you are performing the modifications from another thread; something like this:
Dim t As Thread = New Thread(AddressOf q)
t.Start()
t.Join() 'Waits for the other thread to complete, such that the next line is reached on the right moment
dgvUrls.Refresh()
I deleted Dim ts As ThreadStart = New ThreadStart(AddressOf q) because is not necessary. Also you don't need the Sleep and DoEvents:
Private Sub q()
For i = 1 To 98
tbl.Rows.RemoveAt(0)
Next
End Sub
As a proof of concept (to understand how all this works) is OK; but you should review various ideas in your logic before going further: removing so many rows from the DataSource can provoke problems (you would see that it triggers errors); ideally, (at least, I prefer it) you should modify the DataGridView directly (if possible) to avoid info-synchronisation problems; if you deal with multiple threads you would have to set up a "more proper structure" (the proposed t.Join() should be seen as a temporary fix to make this work).

How to make a very simple asynchronous method call in vb.net

I just have a simple vb.net website that need to call a Sub that performs a very long task that works with syncing up some directories in the filesystem (details not important).
When I call the method, it eventually times out on the website waiting for the sub routine to complete. However, even though the website times out, the routine eventually completes it's task and all the directories end up as they should.
I want to just prevent the timeout so I'd like to just call the Sub asynchronously. I do not need (or even want) and callback/confirmation that it ran successfully.
So, how can I call my method asynchronously inside a website using VB.net?
If you need to some code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Call DoAsyncWork()
End Sub
Protected Sub DoAsyncWork()
Dim ID As String = ParentAccountID
Dim ParentDirectory As String = ConfigurationManager.AppSettings("AcctDataDirectory")
Dim account As New Account()
Dim accts As IEnumerable(Of Account) = account.GetAccounts(ID)
For Each f As String In My.Computer.FileSystem.GetFiles(ParentDirectory)
If f.EndsWith(".txt") Then
Dim LastSlashIndex As Integer = f.LastIndexOf("\")
Dim newFilePath As String = f.Insert(LastSlashIndex, "\Templates")
My.Computer.FileSystem.CopyFile(f, newFilePath)
End If
Next
For Each acct As Account In accts
If acct.ID <> ID Then
Dim ChildDirectory As String = ConfigurationManager.AppSettings("AcctDataDirectory") & acct.ID
If My.Computer.FileSystem.DirectoryExists(ChildDirectory) = False Then
IO.Directory.CreateDirectory(ChildDirectory)
End If
My.Computer.FileSystem.DeleteDirectory(ChildDirectory, FileIO.DeleteDirectoryOption.DeleteAllContents)
My.Computer.FileSystem.CopyDirectory(ParentDirectory, ChildDirectory, True)
Else
End If
Next
End Sub
I wouldn't recommend using the Thread class unless you need a lot more control over the thread, as creating and tearing down threads is expensive. Instead, I would recommend using a ThreadPool thread. See this for a good read.
You can execute your method on a ThreadPool thread like this:
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf DoAsyncWork)
You'll also need to change your method signature to...
Protected Sub DoAsyncWork(state As Object) 'even if you don't use the state object
Finally, also be aware that unhandled exceptions in other threads will kill IIS. See this article (old but still relevant; not sure about the solutions though since I don't reaslly use ASP.NET).
You could do this with a simple thread:
Add :
Imports System.Threading
And wherever you want it to run :
Dim t As New Thread(New ThreadStart(AddressOf DoAsyncWork))
t.Priority = Threading.ThreadPriority.Normal
t.Start()
The call to t.Start() returns immediately and the new thread runs DoAsyncWork in the background until it completes. You would have to make sure that everything in that call was thread-safe but at first glance it generally seems to be so already.
I also was looking for information on Asynchronous programming in VB. In addition to this thread, I also found the following: beginning with Visual Studio 2012 and .Net Framework 4.5, VB was given two new keywords to make a method asynchronous right in the declaration, without using Thread or Threadpool. The new keywords are "Async" and "Await". You may refer to the following links if you wish:
http://msdn.microsoft.com/library/hh191443%28vs.110%29.aspx
https://msdn.microsoft.com/en-us/library/hh191564%28v=vs.110%29.aspx
This is an older thread, but I figured I'd add to it anyway as I recently needed to address this. If you want to use the ThreadPool to call a method with parameters, you can modify #Timiz0r's example as such:
System.Threading.ThreadPool.QueueUserWorkItem(Sub() MethodName( param1, param2, ...))

Appending text to a richTextBox in a different thread and code file

With the intention of creating a program to interface with a serial port device, I recently started learning vb.net. To keep the structure neat the vb code has been split into two places; the first is the code behind for initialising, clicking buttons etc., whilst the second is for managing the comm port. Respectively, these are named 'MainWindow.xaml.vb' and 'ComPortManager.vb'.
In 'comPortManager.vb':
Dim RXArray(2047) As Char ' Array to hold received characters
Dim RXCnt As Integer ' Received character count
Private Sub comPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs) Handles comPort.DataReceived
Do
RXCnt = 0
Do
'Populates the array, RXArray and counts the number of characters, RXCnt
Loop Until (comPort.BytesToRead = 0) 'Keeps reading the buffer until it is empty
'Code for posting to the richTextBox
Loop Until (comPort.BytesToRead = 0) 'If the buffer has been written to in the meantime, repeat
End Sub
The 'MainWindow.xaml' contains a ribbon (Microsoft's October 2010 release) with controls for settings, opening, closing and sending (keeping it all separate and simple for now), with the rest of the window being a richTextBox entitled 'RichTextBox1'.
The search for a way to post the contents of RXArray to RichTextBox1 brought up many suggestions based around Invoke or BeginInvoke. Indeed, working examples have been run successfully but all the code associated with Invoke has been in one file, the code behind. (Correct me if I'm wrong, but this sounds fine for small programs but could get bloated with medium to larger programs, hence me wanting to find a better solution)
The code closest to running (I believe) is as follows:
'In comPort_DataReceived... after populating the array
If RichTextBox1.InvokeRequired Then
RichTextBox1.Invoke(New MethodInvoker(AddressOf Display))
End If
'and back in the main code
Public Delegate Sub MethodInvoker()
Private Sub Display()
RichTextBox1.AppendText(New String(RXArray, 0, RXCnt))
End Sub
This has a few problems and I'm not sure in what direction to go at this stage. RichTextBox1 is in a different thread hence not recognised; InvokeRequired is not a member of System.Windows.Controls.RichTextBox, likewise with Invoke; and finally, in examples, the delegate entitled MethodInvoker was never stated as above.
Any help on this topic is most appreciated. In these few weeks, Invoke, BeginInvoke etc. have somewhat eluded my comprehension. Regards, Jonathan
we have a large scale application which a textbox has the status of many threads appended to it concurrently, and from different forms. this is a dumbed down version of it:
Public Sub addToMessageBox(ByVal msg As String)
If Me.InvokeRequired Then
Dim d As New AddToMessageBoxDelegate(AddressOf Me.addToMessageBox)
Me.BeginInvoke(d, New Object() {msg})
Else
Try
Me.MessageBox.AppendText("--" + msg + vbCrLf)
Catch ex As Exception
End Try
End If
End Sub
The delegate is declared at the begining
Private Delegate Sub AddToMessageBoxDelegate(ByVal msg As String)
the biggest difference that I can see is that I use the parent class's beginInvoke() and InvokeRequired(). I'd say give this a try. Call the parentClass.AddToMessageBox("Text you want to append") where you are calling the display().