I have a vb.net application processing a large amount of data. Due to the memory requirements of the process I am doing this batch-wise, with an overall planned structure as follows:
Do while Start < TotalNumberOfObjects
[cache data used for the upcoming batch]
For i = Start to Stop
[process data using multiple tasks...for example:]
t=taskfactory.startnew(doStuff(i))
TaskList.TryAdd(t.ContinueWith(Sub()
Me.BeginInvoke(DelegateUpdateProgress, {progress})
End Sub))
Next
[Wait for tasks to complete...
Normally I would wait for the tasks using task.waitall(),
but this will cause the UI to wait to update until all tasks are complete]
Start = Stop+1
Stop = Stop+Increment
[clear data from batch that was just completed]
loop
What's the proper way to:
Wait for all the tasks to complete before moving to the next batch?
Update the UI with the overall progress as each task completes?
My target framework is .NET 4.0.
I appreciate any input.
EDIT: Currently I am updating the UI upon completion of each task using task.continuewith() and calling me.beginInvoke to update the form,
TaskList.TryAdd(t.ContinueWith(Sub()
Me.BeginInvoke(DelegateUpdateProgress, {progress})
End Sub))
However, this is incompatible with how I would expect to wait for a list of tasks to complete, task.waitall(tasklist) because calling task.waitall will cause the UI thread to wait to update until all the tasks are complete.
First you need to set up a delegate and then use Dispatcher.Invoke
In the example below, a button is changed from enabled to disable (or the other way around):
Delegate Sub SetRecordButtonEnabledCallback(ByVal Enabled As Boolean)
Friend Sub SetRecordButtonEnabled(ByVal Enabled As Boolean)
Me.btnDGRecord.IsEnabled = Enabled
End Sub
after that all you need to do is call the following code from within your timer to invoke it:
Dim DesiredValue as Boolean = True
Me.Dispatcher.Invoke(New SetRecordButtonEnabledCallback(AddressOf SetRecordButtonEnabled), New
Object() {DesiredValue})
Why don´t you put your routine in a Backgroundworker structure?
So, while you code process data, you´re ready to update any UI component you have.
If necessary, you may also update UI from Backgroundworker, but you need to have some special requirements to do this.
You may consult here:
https://msdn.microsoft.com/en-us//library/ywkkz4s1.aspx
Related
I'm trying out some async code to avoid locking up the UI while my program runs a time-consuming function (using Visual Studio 2022).
Here's what I've got so far - the program is running through pdf filename entries from a datagrid and performing the function on the filenames it finds:
Async Sub process_files()
For Each myrow In DGV_inputfiles.Rows
inputPDF = myrow.cells("col_filenamefull").value
outputPDF = myrow.cells("col_outname").value
Await Task.Run(Sub()
time_consuming_function(inputPDF, outputPDF)
End Sub)
Next
End Sub
At the moment, the program is not waiting for the 'time_consuming_function' to finish, so it's getting to the end of the sub before some of the output files are generated - so it appears to the user that it has finished when it's actually still working.
I believe the solution is something to do with returning a value from the function and waiting for it, but I can't quite see how it works - could anyone help please?
in time_consuming_function(...) you can send some infos to UI using invoke like this (assuming textbox1 exists in UI form):
sub time_consuming_function(...)
.... your stuff....
Me.BeginInvoke(Sub() textbox1.Text = "running...")
....
end sub
The effect of Await is that it returns control to the UI until the expression or call that is Awaited completes. It seems to be suited reasonably well to your workflow, you just need to make changes to process_files to be more user-friendly.
For example, you could have something in the UI update with the file that is currently being processed, and change it at the line before Task.Run.
e.g.
'(Inside the loop body)
CurrentOperation = $"Processing {inputPdf} into {outputPdf}..."
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(CurrentOperation)))
Await Task.Run(...)
You could disable UI controls before the For loop and re-enable them when it finishes.
The benefit of Await is that these changes will be easy, and the logical flow of the routine will be easy to follow.
Be aware that any Await presents an option for re-entrant code as the user may interact with the UI (this is true even for cases where everything is running on one thread as with async internet or I/O operations).
If you haven't done so already, I would recommend to read everything Stephen Cleary has written about asynchronous operations in .NET.
I am working on a VB.NET Windows Forms application where the user is supposed to be able to determine how many processes the application is allowed to launch at a time.
My current method mostly works but I've noticed that occasionally the application goes over the set amount. I use two global variables for this, _ConcurrentRuns which is 0 at the start of the application, and _MaxConcurrentRuns which is set by the user.
Private _sync As new Object()
' This is called Synchronously
Private Function RunModel() As Boolean
If CancelExectuion Then Return CancelCleanup()
Do While True
SyncLock _sync
If _ConcurrentRuns < _MaxConcurrentRuns Then
Interlocked.Increment(_ConcurrentRuns)
Exit Do
End If
End SyncLock
Threading.Thread.Sleep(50)
Loop
'This is what will launch an individual process and close it when finished
ret = RunApplication(arg)
' The process has been closed so we decrement the concurrent runs
Interlocked.Decrement(_ConcurrentRuns)
Return ret
End Function
The goal is to let only one thread exit the while loop at a time, I'm not able to catch it in the debug mode however in the task manager it will occasionally go 1-3 processes over what it's supposed to use. This makes me assume that somehow multiple threads are getting inside the synclock somehow, but I have no clue how that could be happening.
I will be very grateful for any and all help that can be provided, thanks for taking the time to read my question.
So it appears that my solution works for this, I don't want to delete this question because it might be helpful to somebody else in the future.
Answer: Use better process monitoring software / set priority to high in task manager.
I am using Point Grey's FlyCapture API to drive some cameras.
In a public class, I implemented all the starting and initializing code ; in the following _cam refers to a ManagedGigECamera.
Because I have 16 cameras, I want the code to be as fast as possible, so I wanted to use tasks.
Here is the code I use:
_cam.StartCapture(AddressOf OnImageGrabbed)
.../...
Public Sub OnImageGrabbed(ByVal raw_image As ManagedImage)
Dim t As Task = Task.Run(Sub()
'save image to disk or whatever
End Sub)
t.Wait()
End Sub
The above gives -sort of- satisfaction. By viewing image timestamps, I can see that some images are saved seconds after they are grabbed, and even some images are skipped altogether...
I wanted to make sure each call to OnImageGrabbed would start a new task, and tried the following, but it crashes right away with 'object not set to an instance of an object' (can't really debug, the code is running on a remote machine)
_cam.StartCapture(AddressOf OnImageGrabbed)
.../...
Public Async Sub OnImageGrabbed(ByVal raw_image As ManagedImage)
Await Task.Run(Sub()
'save image to disk or whatever
End Sub)
End Sub
All in all, my questions are:
how can I run an event handler asynchronously ?
why, using the first code, do I get (what appears to be) random delays between each call
to OnImageGrabbed ? I mean the differences in time between image timestamps is never the same, and tend to increase on the long run (first few images are almost synchronized, but after 1 minute or so, each image is separated by more and more time). Memory leak ? GC ?
Thanks in advance for any hint !
EDIT:
In the end I changed the way the system works: I fire a software trigger on each camera using a timer, and each trigger is fired 'in parallel':
Parallel.ForEach(Of ListOfCameras)(SingleCamera,
Sub(aCamera, loopstate, num)
aCamera.FireTrigger()
End Sub)
Starting a task and then immediately blocking on it (via Wait) nets you nothing. You may as well just run the saving-image code directly.
The second example is actually asynchronous. You're probably getting an exception because the ManagedImage argument or one of its child objects is being disposed. Remember that the code raising the event has no idea that your code is asynchronous; it's up to you to copy out what you need from the event arguments if you're going to use it asynchronously.
I have started a process:
Dim getUdpate as Process
getUpdate = New Process
getUpdate.StartInfo.FileName = "C:\UTIL\GETBTCH.BAT"
getUpdate.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
getUpdate.StartInfo.UseShellExecute = False
getUpdate.StartInfo.WorkingDirectory = "C:\UTIL\"
getUpdate.Start()
getUpdate.Close()
Then, I want to Run another process but I want to check first if the getUpdate process is already finished.
How do I check if the process is already finished?
I already tried to look at the processes ID, but it only display cmd.exe and there are a lot of cmd.exe as the processes ID so I can't just go and stop all of those.
You can check the HasExited property of the process. It will return true if the process has ended, and false if it is still running.
You will need to check this before you call Close() on your getUpdate Process object. So getProcess will have to remain open until the procsses has exited.
Try:
getUpdate.WaitForExit(); instead of
getUpdate.Close()
If you are making a WinForms application or similarly interactive UI I suggest hooking a function into the object's Exited event instead of polling HasExited.
(You may already know this but) if you use WaitForExit or poll HasExited your UI is hanging exactly because your code is actually waiting for the process to end.
Your UI only has one thread and cannot "multitask". That's why these "processing" type of actions should be done in a different thread (or, as is the case here, a different process) and report back to the UI when they finish.
Example:
' Handle Exited event and display process information.
Private Sub myProcess_Exited(ByVal sender As Object, ByVal e As System.EventArgs)
'Do something in your UI
End Sub
and in your starting code:
getUpdate.EnableRaisingEvents = True
AddHandler getUpdate.Exited, AddressOf myProcess_Exited
I'm trying to figure why my form freezes up when executing some code. I also can't minimize or move the form. Is it because of the WaitForExit being used in the process?
The below code is tied to a button click.
If Checkbox1.checked = True Then
Call Test()
End If
If Checkbox2.checked = True Then
Goto NextStep
Else
Goto StopProcessing
End If
Here is the test sub I'm calling. Calls an exe with an optional argument.
Using psinfo As New Process
psinfo.StartInfo.FileName = "C:\Temp\Test.exe "
psinfo.StartInfo.Arguments = Arg1
psinfo.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
psinfo.Start()
psinfo.WaitForExit()
End Using
The WaitForExit was added (so I thought) to not process the next statement (next statement being the If statement for Checkbox2) until the process was complete. Is this not the case?
The WaitForExit was added (so I thought) to not process the next statement (next statement being the If statement for Checkbox2) until the process was complete.
When you call WaitForExit, it will block until the process (Test.exe) completes.
Since you're running this on the user interface thread, it will cause your form to "freeze" until the process completes fully.
If you need this to not occur, you would need to wait on a background thread. You could, potentially, move this code into a BackgroundWorker and use it to synchronize with your main window - but you will need to handle "waiting" for the process to finish in a different manner (ie: disable your UI up front, run the process, re-enable when complete).
Note that, with the Process class, another alternative would be to add EnableRaisingEvents on the process, then adding a handler to Process.Exited. This will let you not WaitForExit(), but instead get notified via an event when the process completes.