process.start freezing my application(VS 2013) - vb.net

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

Related

run external program and exit from calling program

n the closing procedure of a program (just before running Application.exit) I need to run an external program to pass a parameter and exit the calling program.
The program called (FilmDB_Update.exe) has the task of overwriting the main program or a dll library.
I tried to use the "process.start" technique, but apparently, the calling program remains in use and does not allow me to overwrite it.
This is the code that I write:
Private Sub AggiornaPgm()
Dim ws_file As String = "FilmDB_Update.exe"
Dim ws_proc_param As String = """" + ws_working_path + """ " + ws_temp_path
Dim ws_fullPath As String = Path.Combine(ws_temp_path, ws_file)
If File.Exists(ws_fullPath) Then
File.Copy(ws_fullPath, ws_file, True)
End If
Dim proc As New System.Diagnostics.Process()
proc = Process.Start(ws_file, ws_proc_param)
End Sub
I wanted to try using the shell command, but I can not pass the parameters to the called program.
Does any of you have any other ideas about it?
Thank you
Marcello
As Ahmed suggested, I added the test for the calling process to the program called.
p = Process.GetProcessesByName(ws_calling_pgm)
While p.Count > 0
Threading.Thread.Sleep(3000)
p = Process.GetProcessesByName(ws_calling_pgm)
End While
p = Nothing
When I exit the While loop, the calling process is terminated. I do not understand why, despite the process no longer exists, the main program is still in use.

Multithreading order of actions on GUI

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.

Filestream read only locking PC

I'm trying to read the Windows update log on remote PCs on my LAN. Most of the time I can successfully read the file but at times the program locks up. Likely due to one issue or another - doesn't really matter. What I do need is a way to recover when the Filestream/Streamreader locks up - I'm not sure which is causing the lock. Some streams can set a timeout but the filestream below returns False on a .CanTimeout call.
How can I break out if the stream locks up? (Sometimes the lock is so tight a power off is needed to recover.)
Is there a way to test if the stream will fail before I actually attempt the read?
Is there an alternate way to read a remote log file that another program has open? (I'm using the stream method because the regular File.IO was blocked because the file is open on the remote PC.)
I'm getting closer (I think) with this code. I browed the pathExists code from the referenced post but it was the OP and not an answer.
Imports System.IO
Import System.Threading
...
Function GetAULog(PCName As String) As String
Try
Dim sLogPath As String = String.Format("\\{0}\c$\Windows\SoftwareDistribution\ReportingEvents.log", PCName)
If PCName = My.Computer.Name Then
sLogPath = String.Format("C:\Windows\SoftwareDistribution\ReportingEvents.log", PCName)
End If
' read file open by another process
If Not pathExists(sLogPath) Then
MsgBox("AU log file not found - PC on?")
Return "NA"
End If
Using fs As New FileStream(sLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using sr As New StreamReader(fs)
Dim s As String = sr.ReadToEnd
Return s
End Using
End Using
Catch ex As Exception
MsgBox(ex.Message)
Return ""
End Try
End Function
Public Function pathExists(path As String) As Boolean
Dim exists As Boolean = True
Dim t As New Thread(New ThreadStart(Sub() exists = System.IO.File.Exists(path)))
t.Start()
Dim completed As Boolean = t.Join(500)
'half a sec of timeout
If Not completed Then
exists = False
t.Abort()
End If
t = Nothing
Return exists
End Function
At least when the PC is off the pathExists() code returns False in short order.
My problem now is the process does not end when the program exits - at least in the IDE, didn't check runtime.
I added t = Nothing but that didn't help. I couldn't figure out the proper Using syntax to test that. How do I properly cleanup after a thread timeout?
I've had the situation with this locking until restart problem. It seems to be caused by the tcpip auto tuning feature. You can cure this issue by running
netsh interface tcp set global autotuninglevel=disable
Run this on both machines if you have access. I tried a few workarounds for this issue with checking locks etc but the only way I could solve it was to disable this. The issue is not really with locking but with something at a lower level in the file sharing protocol.
See this article for more detail
"Final" code shown below. The exceptions are not firing when the timeout occurs so the .Abort was evidently OK.
When the timeout does occur, because the remote PC did not respond, there is a process left hanging which goes away after 30 seconds or so. I notice this when using the IDE, I run the program and test a PC that is off. If I then exit the program the form closes but the IDE hangs for ~30 seconds - I can click Stop-Debugging at this point and it works, but the IDE continues on its own after the ~30 second timeout.
I guess the t = Nothing in the Finally block does not dispose of the thread. t.Dispose does not exists.
So, things are working OK with the exception of the dangling thread that eventually clears itself up. The program is no longer hanging to the point where it cannot not be stopped.
'Imports System.IO
'Imports System.Threading
Public Function pathExists(path As String) As Boolean
' check for file exists on remote PC
Dim exists As Boolean = False
Dim t As New Thread(New ThreadStart(Sub() exists = System.IO.File.Exists(path)))
Try
t.Start()
Dim completed As Boolean = t.Join(500)
'half a sec of timeout
If Not completed Then
exists = False
t.Abort()
End If
Catch ex2 As ThreadInterruptedException
MsgBox("timeout on AU log exists test" & vbNewLine & ex2.Message,, "ThreadInterruptedException")
Catch exAbort As ThreadAbortException
MsgBox("timeout on AU log exists test" & vbNewLine & exAbort.Message,, "ThreadAbortException")
Catch ex As Exception
MsgBox("exception on AU log exists test" & vbNewLine & ex.Message)
Finally
t = Nothing
End Try
Return exists
End Function

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).

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")