Multithreading order of actions on GUI - vb.net

I am currently coding a program that downloads multiple csv-Files from different servers using WGET. Every download is a new thread because I call WGET as a process using the WaitForExit(10000) method, so WGET has 10s time to download. If the download did not finish within that time, the thread is killed because the server didn't answer in time.
Also, there is a listview that logs what my program is doing at the moment and which thread has ended with which status.
So that is my method to log (lvw_log is my ListView):
Public Delegate Sub LogDelegate(ByVal Text As String)
Public Sub Log(Text As String)
If lvw_Log.InvokeRequired Then
lvw_Log.BeginInvoke(New LogDelegate(AddressOf Log), New Object() {Text})
Else
lvw_Log.Items.Add(DateTime.Now + ": " + Text)
lvw_Log.TopIndex = lvw_Log.Items.Count - 1
lvw_Log.Refresh()
End If
End Sub
The delegate is called when text has to be added to my ListView from one of the WGET-Threads. 'p' is an object of an own class to hand over a set of variables accesible for the thread.
I store every thread in an ArrayList called WGETThreadArray:
Dim WGETThreadArray As New ArrayList
For i = 0 to NumberOfFilesToDownload - 1
Dim WGETThread As New System.Threading.Thread(AddressOf StartWGET)
WGETThreadArray.Add(WGETThread)
Log("Starting thread " + i.ToString)
WGETThreadArray(i).Start(p)
Next
Now I want to wait for all threads to finish or to be aborted:
Log("Waiting for threads to finish")
For i = 0 To WGETThreadArray.Count - 1
WGETThreadArray(i).Join()
Next
Log("All threads closed")
Log("Downloaded all DB-Info-Files")
The thread (method StartWGET) is this:
Public Sub StartWGET(p As Object)
'this method is called for each thread to parallely download the necessary files
Dim procInfo As New ProcessStartInfo(p.PathToWgetExe, p.ArgumentString)
procInfo.CreateNoWindow = False
procInfo.UseShellExecute = True
Dim WGETProcessHandler As System.Diagnostics.Process = System.Diagnostics.Process.Start(procInfo)
If Not WGETProcessHandler.WaitForExit(10000) Then 'if WGET doesn't finish within '10000' milliseconds, the thread gets killed
WGETProcessHandler.Kill()
Log("DB " + p.DBName + " was not loaded. Thread " + p.ThreadIndex.ToString + " killed ")
DatabaseArray(p.ThreadIndex).isLoaded = False
WGETThreadArray(p.ThreadIndex).Abort()
Else
DatabaseArray(p.ThreadIndex).isLoaded = True
Log(p.URL + " downloaded. Thread " + p.ThreadIndex.ToString + " ended successfully.")
End If
End Function
As you can see, the method "Log" is called within the threads. The problem is that the main thread always writes to the ListView before the other threads. So I see the line "All threads closed" before a message like "Thread ended successfully". Eventhough I used .Join() method in the for-Loop. I wanted to make it wait until all threads have finished.
So how can I make the main thread pause until all other WGET-Threads are done. And also, how can I make them log before the main thread takes over and tells me that all threads have finished.
I know it is hard to explain and I really hope I made myself clear. If not, please ask me again so I'll explain my self better.

Log("Waiting for threads to finish")
Dim SomeAlive as boolean
do
Threading.Thread.Sleep(100)
SomeAlive = False
For i as integer = 0 To WGETThreadArray.Count - 1
if WGETThreadArray(i).IsALive then
SomeALive = True
Exit For
end if
Next
Loop While SomeAlive
YOur statement
WGETThreadArray(p.ThreadIndex).Abort()
Is pointless since the thread will abort at the end of the routine anyway.
Also spawning multiple processes to download multiple files will probably NOT buy you much over doing them one at a time.

The main thread is blocked by the Join() calls so the BeginInvoke of Log, which tries to switch to the main thread, has to wait for the main thread to become available (after all the joins) This means that the actual calls to Log will be executed AFTER the main thread becomes available. And that is after waiting for the threads and logging the "All threads closed" message
Call the waiting for the worker threads on a new thread to free up the main thread to do just UI rendering. This is an important principle: to have a responsive UI, have the main thread do only very little work and never block it.

Related

Killing a thread completely in Multithreaded application

Can someone show/tell me where I'm steering wrong with this? I am running a second thread in my application where all it does is read values from registers in a motion controller and then constantly updates those values to the appropriate fields on my UI every 100ms. When I disconnect the controller (ie...Ethernet cable disconnected, so lost comms), I want to destroy/terminate the thread (thread1) completely. When I reconnect the Ethernet cable, I click a button on my UI to reestablish comms to the controller, and execute Runthread() Sub. That is when I want to recreate the thread and start it. However, when debugging this part of the code, it appears that the thread (thread1) is never destroyed even though I have verified that the code does get to that line (Case 1) and execute it. After comms is re-established, the code goes right to Case 3 and starts thread1, where I would expect it to jump to Case 2 and recreate the thread.
Public Class frmMain
Private RMC As RMCLink
Public thread1 As System.Threading.Thread = New System.Threading.Thread(AddressOf ReadRegisters)
Private Sub RunThread()
Dim conditions As Integer
If RMC.IsConnected(PingType.Ping) = False And thread1 IsNot Nothing Then
conditions = 1
ElseIf RMC.IsConnected(PingType.Ping) = True And thread1 Is Nothing Then
conditions = 2
ElseIf RMC.IsConnected(PingType.Ping) = True And thread1 IsNot Nothing Then
conditions = 3
End If
Select Case conditions
Case 1
Thread.Sleep(100)
thread1.Abort()
Case 2
Dim thread1 As System.Threading.Thread = New System.Threading.Thread(AddressOf ReadRegisters)
thread1.Start()
Case 3
thread1.Start()
End Select
End Sub
The documentation (https://msdn.microsoft.com/en-us/library/system.threading.thread.abort(v=vs.110).aspx) states that it usually terminates the thread, but as you've found this doesn't always happen.
One way to accomplish this (I'm sure there are others), is to create a public PleaseDie property that your thread's subroutine(s) check periodically, and if that property is set to True, then exit the subroutines within the thread. Before the thread is started, you'll of course have to reset PleaseDie to False.

Multiple threads in a for-loop using a parameterized function(x, y, z)

I have a list which contains folder ID's and folder paths. I would like to pass some of these folders to a function which zips them. What I want is to have three threads run in parallel and zip three different paths at a time. What happens now is each thread waits until the next one has finished in order to process the next. Any ideas?
Dim SelectedRange = From folders In listFolders Where folders.FolderID >= 150101
For Each item In SelectedRange
Dim t As New Thread(
Sub()
Me.BeginInvoke(DirectCast(Sub() ZipFolder(sInclearDestination, item.FolderID.ToString, item.FolderPath), MethodInvoker))
End Sub)
t.Start()
t.Join()
Next
Public Function ZipFolder(ByVal sFolderPathDestination As String, ByVal folderID As String, ByVal folderPath As String) As Boolean
Try
Using zip = New Ionic.Zip.ZipFile()
'If the zip file does not exist then get the folder and zip it to the destination
If Not File.Exists(Path.Combine(sFolderPathDestination, folderID & ".zip")) Then
zip.AddDirectory(folderPath)
zip.Save(Path.Combine(sFolderPathDestination, CType(folderID, String) & ".zip"))
Return True
Else
Logging.Log("Aborting zipping: " & Path.Combine(sFolderPathDestination, folderID & ".zip") & ". The zip file already exists!")
Return False
End If
End Using
Catch ex As Exception
Logging.Log("Error in zipping: " & Path.Combine(sFolderPathDestination, folderID & ".zip") & " Error: " & ex.Message)
Return False
End Try
End Function
There are two problems with your code.
The first problem is the call to Me.BeginInvoke. Presumably you are creating a WinForm application and Me is a reference to the current Form. The Form.BeginInvoke (inherited from the base Control class) causes the given delegate to be executed on the UI thread. So, all you are doing is creating three separate threads which all immediately invoke back to the UI thread to do all of their work. You obviously can't do that and still expect the tasks to be processed in parallel. You need to remove the call to BeginInvoke. If you need to call BeginInvoke in-order to update the display of some data on the form, you need to do it as late as possible and do as little work as possible within that UI-invoked code so that the majority of the work is still being done in the worker threads.
The second problem is the call to Thread.Join. You are calling Join inside your For loop right after starting the thread. That means that it will sit there and wait, at that call to Join, until the worker thread is complete. Therefore, your loop waits for each thread to complete before starting the next one, in essence, making it single threaded. You should just remove the call to Join. If you need the calling method to wait for all the threads to complete, just wait to call Join on the threads until all of them have been started (i.e. after the For loop).

process.start freezing my application(VS 2013)

So I am trying to make an application that starts a 3rd party exe to do some file operations,
based on a list of filenames.
So if the list has 13 items I am going through a loop 13 times, each time starting the external process, notifying the user which file is processed right now, starting the process and waiting for it to exit. To notify the user, another listbox is used as a shoutbox. The problem is, that .waitforexit() somehow freezes the whole thread in a strange way, so that the external program is called nmormaly, tyhe files get proccesed normaly but the main window is frozen until all items are done. So basically the Shoutbox is frozen and gets spammed with all the info only after the whole loop is finished. I've tried numerous ways to implement this, such as starting new threads, using threadpool, timers and whatnot. Any help is appreciated.
code:
Imports System.Windows.Threading
Imports System.Windows.Forms
Imports System.IO
Imports System.Threading
If Listbox2.Items.Count > 0 Then
tabctrl.SelectedIndex = 2
Listbox3.Items.Add(DateTime.Now.ToString & ": Process initiated.")
For i = 0 To Listbox2.Items.Count - 1
Listbox3.Items.Add(DateTime.Now.ToString & ": Processing :" & Listbox1.Items.Item(i))
If System.IO.File.Exists(Listbox2.Items.Item(i)) = False Then
Dim pInfo As New ProcessStartInfo()
With pInfo
.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
.FileName = System.IO.Directory.GetCurrentDirectory & "\" & "myapp.exe"
.argouments = "w/e"
End With
Dim p As Process = Process.Start(pInfo)
p.WaitForExit()
p.Dispose()
Else
Listbox3.Items.Add(DateTime.Now.ToString & ":! " & Listbox2.Items.Item(i) & " already exists. Moving to next file..")
End If
Next
Listbox3.Items.Add("*-*")
Listbox3.Items.Add(DateTime.Now.ToString & ": Done.")
End If
The problem is that you (at least in the code you posted) are calling WaitForExit() on the UI thread. The UI thread is responsible for redrawing the window, so if you block it, by calling WaitForExit() for example, its not redrawing the ui and the app appears to be frozen.
What you need to do is call it on another thread or on the thread pool, I recommend using Tasks:
Task.Run( Sub()
Dim p As Process = Process.Start(pInfo)
p.WaitForExit()
End Sub)
However, since you're not doing anything with the results of the Process.Start() call, you can also consider not calling WaitForExit() at all.
Since you're using VS2013 you can also use the await operator to wait for the process to finish:
await Task.Run( Sub()
Dim p As Process = Process.Start(pInfo)
p.WaitForExit()
End Sub)
Note that you also have to add the async keyword to the surrounding method as well

Detect when exe is started vb.net

Dose anybody know how I can make my VB.net application wait until a process is detected as running?
I can find example of how to detect once an exe has finished running but none that detect when an exe is started?
You can use the System.Management.ManagementEventWatcher to wait for certain WMI events to occur. You need to give it a query type and condition to have it watch for the next creation of your process, then get it to do something when that occurs.
For example, if you want :
Dim watcher As ManagementEventWatcher
Public Sub Main()
Dim monitoredProcess = "Notepad.exe"
Dim query As WqlEventQuery = New WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 1), "TargetInstance isa ""Win32_Process"" And TargetInstance.Name = """ & monitoredProcess & """")
watcher = New ManagementEventWatcher()
watcher.Query = query
'This starts watching asynchronously, triggering EventArrived events every time a new event comes in.
'You can do synchronous watching via the WaitForNextEvent() method
watcher.Start()
End Sub
Private Sub Watcher_EventArrived(sender As Object, e As EventArrivedEventArgs) Handles watcher.EventArrived
'Do stuff with the startup event
End Sub
Eventually you'll need to stop the watcher, which is you can do by closing the app, or calling watcher.Stop(). This has been written as brain compiler, so if there's any issues let me know.
You could simply wait and check every once in a while whether the process exists. Use Thread.Sleep to avoid busy waiting.
However, this has the possibility that you miss the process if it starts and exists during your wait time.
You can use the below condition
return Process.GetProcesses().Any(Function(p) p.Name.Contains(myProcessName))
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("processName")
If p.Count > 0 Then
' Process is running
Else
' Process is not running
End If
End Sub
OR SIMPLY
System.Diagnostics.Process.GetProcessesByName("processName")

Newly created thread not firing until main thread finished

I'm extremely novice at threading and I'm simply creating a single thread to run a large function. I've created a messagebox to appear at the end of the function towards the end of the program to tell me the load time it took. As i load the application, the messagebox will appear with a time it took and THEN the thread will kick off(although the UI is navigable while the components are loading from the thread) isn't the point of threading to be able to process multiple functions at the same time? Why is this waiting until the main thread is finished before the new thread kicks off?
I declare and start the new thread early in the app
For every Form in the application's namespace, there will be a default instance created in the My namespace under the Forms property.
----------------------/ 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 and Tested!
You have to create multiple threads if you want them to run the way you are thinking (simultaneously).Take a look at this link and try creating more than one thread. This should help your problems. Cheers
http://msdn.microsoft.com/en-us/library/ck8bc5c6%28v=vs.80%29.aspx