How do I know if a thread is finished when I create it in a loop?
Simplified code:
Public Sub LoadExcelFiles()
AddMore:
Dim t As New Thread(New ThreadStart(AddressOf CreateExcelFile))
t.Start()
'Check if there are more files.
'If yes: GoTo AddMore
End Sub
How do I know when thread 't' is completed? I want to add the file created by 't' into a treeview.
Another problem is, when the user drops 33 files, I have 11 threads running at the same time (3 excelfiles are used per thread)
You could consider doing it like this:
Delegate Sub CreateExcelFileAsync()
Public Sub LoadExcelFiles()
Dim d As New CreateExcelFileAsync(AddressOf CreateExcelFile)
Dim result As IAsyncResult = d.BeginInvoke(Nothing, Nothing)
'..
'more work
'..
'wait until completed before starting over
If Not result.IsCompleted() Then
result.AsyncWaitHandle.WaitOne()
End If
d.EndInvoke(result)
End Sub
This way you can use the AsyncResult to check the completion.
An interesting article about the answer of #Paul Deen (wish is a good solution) can be found at http://tech.xster.net/tips/multi-threading-in-vb-net/
Tho I did it another way.
Dim t As New Thread(New ThreadStart(AddressOf CreateExcelFile))
If t.IsAlive Then
t.Join()
Else
t.Start()
End If
'Some code here
While t.IsAlive
System.Threading.Thread.Sleep("500")
End While
MessageBox.Show("All Files Completed")
Related
When running my code I seem to encounter deadlocks while trying to update a GUI element from within one of the parallel tasks.
I've tried surrounding the Output function with "Synclock me" to try to ensure that only one task is trying to update the control at a time.
Private Sub RunParallel(records as list(of DataRecord), ou as String)
Dim ParallelOptions As New ParallelOptions
ParallelOptions.MaxDegreeOfParallelism = 10
Parallel.ForEach(records, ParallelOptions, Sub(myrecord)
ProcessRecord(myrecord, ou)
End Sub)
Output("Done...." & vbCrLf)
End Sub
Private Sub ProcessRecord(ByVal record As DataRecord, ByVal ou As String)
'Output($"BromcomID = {record("ID")}, Forename = {record("Forename")}{vbCrLf}")
Dim ud As New UserDetails With {
.EmployeeID = record("ID"),
.SamAccountName = record("SamAccountName"),
.GivenName = record("Forename"),
.Surname = record("Surname")
}
If Not CreateUser(ou, ud) Then
'Threading.Thread.Sleep(2000)
' Output($"Error creating {ud.EmployeeID}{vbCrLf}")
End If
End Sub
Private Sub Output(ByVal s As String)
SyncLock Me
If Me.InvokeRequired Then
Invoke(Sub()
Outbox.AppendText(s)
Outbox.SelectionStart = Len(Outbox.Text)
Outbox.ScrollToCaret()
Outbox.Select()
End Sub)
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Len(Outbox.Text)
Outbox.ScrollToCaret()
Outbox.Select()
End If
End SyncLock
End Sub
The code as supplied seems to run, but if I uncomment out the Output calls in the ProcessRecord() function, it hangs and never gets exits the Parallel.foreach
--- Update
After playing around with suggestions and comments on here I still can't get it to work correctly.
If I take out all of the output from ProcessRecord it seems to work correctly. However with the following code, it now seems to run each ProcessRecord sequentially (not 10 at a time as I intended), and then hangs after the last one.
Output("Dispatching" & vbCrLf)
Dim ParallelOptions As New ParallelOptions With {
.MaxDegreeOfParallelism = 10
}
Parallel.ForEach(recordList, ParallelOptions, Sub(myrecord)
ProcessRecord(myrecord, ou)
End Sub)
'For Each myrecord As DataRecord In recordList
' Task.Factory.StartNew(Sub() ProcessRecord(myrecord, ou))
'Next
Output("Done...." & vbCrLf)
End Sub
Private Sub ProcessRecord(ByVal record As DataRecord, ByVal ou As String)
Dim ud As New UserDetails With {
.EmployeeID = record("ID"),
.SamAccountName = record("SamAccountName"),
.GivenName = record("Forename"),
.Surname = record("Surname"),
.DisplayName = $"{record("Forename")} {record("Surname")} (Student)"}
If Not CreateUser(ou, ud) Then
' Output($"Error creating {ud.EmployeeID}{vbCrLf}")
End If
Output($"BromcomID = {record("ID")}, Forename = {record("Forename")}{vbCrLf}")
End Sub
Private Sub Output(ByVal s As String)
If Me.InvokeRequired Then
Invoke(Sub()
Output(s)
End Sub)
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Outbox.TextLength
Outbox.ScrollToCaret()
Outbox.Select()
Outbox.Refresh()
End If
End Sub
If I use the commented out Task.Factory code everything seems to work perfectly, except I cant control how many tasks at a time are launched, and I can't wait till all of them have finished, the for loop just launches all the tasks, and then carries on with the Output("Done....) line.
The synclock statements didn't seem to affect anything either way.
Give this a try
Private Sub Output(ByVal s As String)
If Me.InvokeRequired Then
Me.Invoke(Sub() Output(s))
'Me.BeginInvoke(Sub() Output(s))
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Outbox.TextLength
Outbox.ScrollToCaret()
Outbox.Select()
Outbox.Refresh()
End If
End Sub
There may be an issue if you have events tied to Outbox, like text changed. Tested Output method with
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim nums As New List(Of Integer)
For x As Integer = 1 To 500
nums.Add(x)
Next
'because it is in a button, run from a task
Dim t As Task
t = Task.Run(Sub()
Parallel.ForEach(nums, Sub(num)
Output(num.ToString & Environment.NewLine)
End Sub)
End Sub)
End Sub
If you want to go ahead with using a Task-based approach, then you certainly can control how many are launched at a time, and wait for all of them to finish. It requires some additional code for the manual management. This is discussed in some detail in Microsoft documentation: https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern
It's not necessarily a bad thing to initiate all of the tasks immediately, then you'll be leaving it to the thread pool to take care of how many to run at a time.
If you want greater control, you can use the "throttling" design from the link. In your "pending" queue, store delegates/lambdas that will themselves kick off Task.Run. Then, as you dequeue from the "pending" queue into the "active" list, you can Invoke on the delegate/lambda to get the Task and Await Task.WhenAny on the "active" list.
One potential benefit of doing things this way is that the work in each top-level Task can be split between UI work running on the UI thread and processor-limited work running on the thread pool.
(I'm not suggesting that this is necessarily the best option for you, just trying to expand on what you should be looking at doing if you really want to pursue using Task instead of Parallel.)
I have this code which loops through all my accounts in my list and then does something to the accounts using tasks for each account as a way to speed up the process. Each time the program completes this action, I want the user interface to update the progress bar. I was using Invoke before but it isn't the best option and I couldn't get it working. Now I know this can be done using a background worker but this isn't the best way of making your application multithreaded so I used this. And instead of invoking I heard about ContinueWith but I can't seem to get it working and I get no error message just a red underline.
Code:
progressBar.Value = 0
Dim tasks As New List(Of Task)()
For Each account In combos
Dim t As Task = Task.Run(Sub()
While checked = False
If proxies.Count = 0 Then
Exit Sub
'Also can't think of a good way to stop searching through accounts when there are no proxies left in my queue.
End If
Dim proxy As New WebProxy(proxies(0))
proxies.TryDequeue(0)
'Do something
End While
checkedAmount += 1
Dim progress As Integer = ((checkedAmount / combos.Count) * 100)
Task.ContinueWith(progressBar.Value = progress, TaskScheduler.FromCurrentSynchronizationContext()) 'Error here
End Sub)
tasks.Add(t)
Next
Task.WaitAll(tasks.ToArray())
I get no error code as shown here:
I have also tried putting a sub after and stuff but that lead to nothing.
Thanks for any help in advance.
Update tried with invoke:
Private Delegate Sub UpdateProgressBarDelegate(ByVal progressBarUpdate As ProgressBar, ByVal value As Integer)
Dim checkedAmount As Integer = 0
Dim checked As Boolean = False
Private Sub startBtn_Click(sender As Object, e As EventArgs) Handles startBtn.Click
progressBar.Value = 0
Dim tasks As New List(Of Task)()
For Each account In combos
Dim t As Task = Task.Run(Sub()
While checked = False
proxies.TryDequeue(0)
'do stuff
End While
checkedAmount += 1
Dim progress As Integer = ((checkedAmount / combos.Count) * 100)
If Me.InvokeRequired = True Then
Me.Invoke(New UpdateProgressBarDelegate(AddressOf UpdateProgressBar), progressBar, progress)
Else
UpdateProgressBar(progressBar, progress)
End If
'Task.ContinueWith(progressBar.Value = progress, TaskScheduler.FromCurrentSynchronizationContext())
End Sub)
tasks.Add(t)
Next
Task.WaitAll(tasks.ToArray())
End Sub
Private Sub UpdateProgressBar(ByVal ProgressBarUpdate As ProgressBar, progress As Integer)
progressBar.Value = progress
End Sub
Still doesn't work not sure why?
Now I know this can be done using a background worker but this isn't the best way of making your application multithreaded
Sort of.
BackgroundWorker is a poor way to run many different Tasks individually. No one wants to deal with a separate BackgroundWorker component for each Task. But one BackgroundWorker is a great way to spawn just one extra thread to manage all your other Tasks and update the progress bar. It's an easy solution here.
Either way, the one thing you'll want to do for sure is move the code to update the ProgressBar out of the individual Tasks. Having that inside a Tasks violates separation of concerns1. Once you do that, you'll also need to change the call to WaitAll() to use WaitAny() in a loop that knows how many tasks you have, so you can still update the ProgressBar as each Task finishes. This will likley have the side effect of fixing your current issue, as well.
Private Async Sub startBtn_Click(sender As Object, e As EventArgs) Handles startBtn.Click
Dim tasks As New List(Of Task)()
For Each account In combos
Dim t As Task = Task.Run(Sub()
While Not checked
proxies.TryDequeue(0)
'do stuff
End While
End Sub)
tasks.Add(t)
Next
progressBar.Value = 0
For i As Integer = 1 To tasks.Count
Dim t = Await Task.WhenAny(tasks)
tasks.Remove(t)
progressBar.Value = (i / combos.Count) * 100
Next i
End Sub
1 The problem here illustrates one reason we care about separation of concerns at all. Once I fix this, the code becomes much simpler and the frustrating errors just go away.
The above waitany is unnecessary.
I have found that you might as well put your progress bar code directly into the task run sub:
Dim ProgressBarSync As New Object
Dim tasks As New List(Of Task)()
For Each account In combos
Dim t As Task = Task.Run(
Sub()
'do stuff
SyncLock ProgressBarSync
ProgressBar.Increment(1)
End SyncLock
End Sub)
tasks.Add(t)
Next
I have a thread I start like this:
Dim documentConverterThread As New Threading.Thread(AddressOf ConvertToLatestVersion)
documentConverterThread.Start(oDoc)
And this is the function the word document is passed to:
Private Sub ConvertWordTemplateToLatestVersion(ByVal oDoc As Word.Document)
Try
oDoc.Convert()
oDoc.Save()
oDoc.Close()
Catch ex As Exception
End Try
End Sub
What I'm trying to do is that if execution gets stuck when calling the .Convert() function, then close the thread and the word document.
I have been trying to use a timer, but I need access to both the documentConverterThread and oDoc objects to handle the Timer.Tick event:
Private Sub TimerEventProcesser(ByVal sender As Object, ByVal e As System.EventArgs)
If documentConverterThread.IsAlive() Then
documentConverterThread.Abort()
oDoc.Close()
End If
End Sub
Is there any way around this other than using a private variable in the TimerEventProcessor function? Any help is appreciated.
You could create another thread for it. Using the Sub() lambda you can create an inline delegate from which you can call the new timer method and pass both the thread variable and the document variable to it. The new thread will then wait 10 seconds, and if the first thread isn't finished it will close the document.
Private Sub ConvertWordTemplateToLatestVersion(ByVal oDoc As Word.Document)
Dim TimerThread As New Threading.Thread(Sub() TimerThread(documentConverterThread, oDoc))
TimerThread.Start() 'Start the timer thread.
Try
oDoc.Convert()
oDoc.Save()
oDoc.Close()
Catch 'Why catch an exception if we do not even handle it?
End Try
End Sub
Private Sub TimerThread(ByRef dcThread As Threading.Thread, ByRef oDoc As Word.Document)
Threading.Thread.Sleep(10000) 'Wait 10 seconds.
If dcThread.IsAlive() Then 'Close the document if the primary thread isn't finished yet.
dcThread.Abort()
Try
oDoc.Close()
oDoc.Dispose()
Catch
End Try
End If
End Sub
I used ByRef instead of ByVal to be sure we are referencing the same objects all the time and not newly created ones.
My case is I have the following method that uses SyncLock to ensure the writing of file by one thread at a time.
Private Shared lockThis As New Object
Public Sub Process()
SyncLock lockThis
File.AppendAllText("c:\jamo\foo.txt","foo")
End SyncLock
End Sub
I'm using many threads running at time:
Public Sub CreateThreads()
Dim trd as Thread
Dim X as Integer = 10
For i as integer = 1 to X
trd = New Thread(AddressOf Process)
trd.Start()
Next Sub
End Sub
My problem is when X is big (like 500), one o more threads write to file at same time. Why is happening this?
I don't have proof, but it could be telling the truth. If any other process opens the file without sharing it, with 500 or more threads attempting to open it, it is likely the file will be locked for one of them...
Static lock As Object
SyncLock lock
TraverSingweb.TraverSingWeb.WebInvoke(Sub() TraverSingweb.TraverSingWeb.putHtmlIntoWebBrowser(theenchancedwinclient)) 'This quick function need to finish before we continue
End SyncLock
SyncLock lock
'SuperGlobal.lockMeFirst(AddressOf SuperGlobal.doNothing) ' donothing
End SyncLock
This is how I currently do in vb.net. Is this a pattern?
Here is a really basic example; the main method simply creates two threads and starts them. The first thread waits 10 seconds and then sets the _WaitHandle_FirstThreadDone. The second thread simply waits for the _WaitHandle_FirstThreadDone to be set before continuing. Both threads are started at the same time but the second thread will wait for the first thread to set the _WaitHandle_FirstThreadDone before continuing on.
See: System.Threading.AutoResetEvent for further details.
Module Module1
Private _WaitHandle_FirstThreadDone As New System.Threading.AutoResetEvent(False)
Sub Main()
Console.WriteLine("Main Started")
Dim t1 As New Threading.Thread(AddressOf Thread1)
Dim t2 As New Threading.Thread(AddressOf thread2)
t1.Start()
t2.Start()
Console.WriteLine("Main Stopped")
Console.ReadKey()
End Sub
Private Sub Thread1()
Console.WriteLine("Thread1 Started")
Threading.Thread.Sleep(10000)
_WaitHandle_FirstThreadDone.Set()
Console.WriteLine("Thread1 Stopped")
End Sub
Private Sub thread2()
Console.WriteLine("Thread2 Started")
_WaitHandle_FirstThreadDone.WaitOne()
Console.WriteLine("Thread2 Stopped")
End Sub
End Module