Looped Tasks Submitted Inside Try/Catch Blocks - vb.net

I have a background task "submitter" that handles one or more scheduled tasks:
...
While sdr.read()
...
Dim oBackground As New Background
Task.Run(Sub() CallByName(oBackground, sProcessKey, CallType.Method, iPQID))
End While
Before you ask, yes, the tasks are thread-safe. :)
I want the submitter to end (go back to sleep) while the tasks operate; awaiting the next heartbeat, but I need to catch and handle exceptions in each thread.
The only way I've come up with to do this is to create a new task for each background job to be run and handle the exception there, but this seems inefficient:
Create new Task for each background job that then, submits the actual job and waits to see if an error happened and handles it:
Private Sub NewJob(sProcessKey, iPQID)
Dim t As Task
....
While sdr.read()
...
Dim oBackground As New Background
t = Task.Run(Sub() CallByName(oBackground, sProcessKey, CallType.Method, iPQID))
Try
t.Wait()
Catch ex As AggregateException
For Each IEx As Exception In ex.InnerExceptions
HandleBackgroundException(IEx, sProcessKey)
Next
End Try
End While
End Sub
Isn't there a better way to do this??
Thanks!

I believe I've found the answer to my own question.
Since each task is it's own thread, no additional thread submittal is needed.
The task is run directly and error handling happens within the task as shown above (but without Await'ing it). Thus:
Public Shared Sub MyTask()
Try
... do some work here ...
Catch ex As AggregateException
For Each IEx As Exception In ex.InnerExceptions
HandleBackgroundException(IEx, sProcessKey)
Next
End Try
End Sub
That's it!
I hope this helps someone else.

Related

Safely aborting threads in a ThreadList

A threadlist is being used so threads can be aborted at the user's request. Thus far, I ported some code from killing processes (also based on removing list items safely), but it is not aborting any of the threads. Using the try catch alone inside a loop from 0 To Threadlist.Count will abort the threads but will also result in an exception related to use of a list whose elements have been removed. Is there anything that I am doing wrong in the following code:
For x As Integer = threadList.Count - 1 To 0 Step -1
Dim tid As String = threadList(x).ManagedThreadId
For Each t As Thread In threadList
If tid = t.ManagedThreadId.ToString Then
Try
t.Abort()
threadList.RemoveAt(x)
Catch ex As ThreadAbortException
'ex.ToString()
End Try
End If
Next
Next
You can't remove an item from the list used in a For Each loop. Get the thread you want to remove and then remove it.
Dim threadToRemove As Thread = Nothing
' First, find the thread to remove
For Each t As Thread In threadList
If tid = t.ManagedThreadId.ToString Then
threadToRemove = t
Exit For
End If
Next
' Then, remove the thread
If threadToRemove IsNot Nothing Then
Try
t.Abort()
threadList.Remove(threadToRemove)
Catch ex As ThreadAbortException
'ex.ToString()
End Try
End If
By splitting your logic it will be ok. You can then put those two pieces in method if you want.
I don't know if this piece of code will fix your problem but I hope you get the idea. Looping your threadList twice will just remove all thread in a complicated way.

vb.net Not waiting until all tasks have completed

So I'm processing records. I'm using a task to process each record.
My issue is that my program is completing before all tasks complete.
Anyone any thoughts on what I'm doing wrong here?
Dim task As Task
Try
'Keep looping until no more requests to run have been made
Do
Dim controller As New Controller()
Dim record As Record = controller.GetNextRecord()
If record IsNot Nothing Then
'Use Task!
task = Task.Factory.StartNew(Sub() controller.ProcessRecord(record), TaskCreationOptions.LongRunning)
CalledWhenBusy = True
End If
TryAgain:
Loop Until ProcessAgain() = False
Catch ex As System.Net.WebException
logger.ErrorException("unable to connect to remoting server", ex)
Finally
logger.Info("Processed all records.. now about to wait for all tasks to complete")
'Wait till all tasks have stopped running
Task.WaitAll(task)
logger.Info("Processed all records.. All tasks have completed")
'The dispatcher has finished for now so clean up
Me.StopUsing()
End Try
Private Function ProcessAgain() As Boolean
If CalledWhenBusy Then
'Reset the flag and exit with true
CalledWhenBusy = False
Return True
End If
Return False
End Function
UPDATE
I've resolved my issue by using a list of tasks as suggested by #HansPassant and #usr
The reason for not using Foreach, is that more records can be added while processing.. hence the do while loop...
Thank you for your help.
Dim taskList = New List(Of Task)()
Try
'Keep looping until no more requests to run have been made
Do
Dim controller As New Controller()
Dim record As Record = controller.GetNextRecord()
If record IsNot Nothing Then
'Use Task!
taskList.Add(Task.Factory.StartNew(Sub() controller.ProcessRecord(record)))
CalledWhenBusy = True
End If
TryAgain:
Loop Until ProcessAgain() = False
Catch ex As System.Net.WebException
logger.ErrorException("unable to connect to remoting server", ex)
Finally
logger.Info("Processed all records.. now about to wait for all tasks to complete")
'Wait till all tasks have stopped running
Task.WaitAll(taskList.ToArray())
logger.Info("Processed all records.. All tasks have completed")
'The dispatcher has finished for now so clean up
Me.StopUsing()
End Try
Task.WaitAll(task) is just waiting for one task. Where are the others? Did you even store them? Not apparent from this code.
Ideally, you transform this code so that it can make use of Parallel.ForEach. You need to put the work items into IEnumerable format for that to work. For example, add them to a List and feed the list to Parallel.ForEach.

vb msgbox after completion of a process

I am running a batch file with shell function in VB2010 with the following command
Shell("C:\test.bat", AppWinStyle.NormalFocus)
This process takes a lot of time to complete, might even take a day to complete depending on the input file.
I want a MsgBox to display a "Job Finished" msg when the process is finished.
something like
MsgBox("Job Finished")
How do I do that. I am very new to VB, so please help me with full code.
Thank you
This will basically wait till the process finishes (It finishes by exiting. as most batch files do. I'm only making an assumption though).
Sub Main()
Dim P As New Process
P.StartInfo.FileName = "C:\test.bat"
Try
P.Start()
P.WaitForExit()
MsgBox("Process completed successfully")
Catch ex As Exception
MsgBox("Error:" & ex.Message)
End Try
End Sub

How do I solve 'System.OutOfMemoryException'

I have a Windows Service application. It is a very busy application. It is supposed to run continuously looking for things to do. After it runs for a while I get
Exception of type 'System.OutOfMemoryException' was thrown.
It can happen at different times but usually a this paragraph:
Private Shared Function GetUnprocessedQueue() As Boolean
Try
Dim l_svcOOA As New svcITGOOA.IsvcITGOOAClient(OOAProcessing.cGlobals.EndPoint_ITGOOA)
Dim l_iFilter As New svcITGOOA.clsFilter
With l_svcOOA
With l_iFilter
.FilingType = OOAProcessing.cGlobals.FilingType
End With
m_ReturnClass = .itgWcfOOA(1, cGlobals.DatabaseIndicator, svcITGOOA.eOOAAction.GetUnprocessedQueue, l_iFilter, 71)
Return CompletedGetUnprocessedQueue(m_ReturnClass)
End With
Catch ex As Exception
ExceptionHandling(ex, "GetUnprocessedQueue " & m_Application)
Return False
End Try
End Function
This is using a wcf service to read a queue. It reads the queue every two minutes to see if new records have been added to it.
Please help me solve this. I don’t know where to start.
The OutOfMemoryException exception occurs when the GC has completed a cycle of collection but the memory is not available even after that. I couldn't make out what the above code snippet does, but I think using Weak References for objects could be useful.
I had a timer that was generated within the same paragraph that I was setting
For example
m_svcTimer = New Timers.Timer With {.Interval = m_Interval, .Enabled = True}
AddHandler m_svcTimer.Elapsed, AddressOf StartTheQueueIfTime
m_svcTimer.Enabled = True
m_svcTimer.Start()
was within the paragraph StartTheQueueIfTime. I thought this would be a way to change the time interval. Instead it kept creating new events. Finally too many caused my crash.
Bob

Want to Call Same BackgroundWorker Multiple Times without using Application.DoEvents

I'm running in to a problem that I was able to fix with Application.DoEvents, but don't want to leave that in because it might introduce all sorts of nasty problems.
Background:
Our app is primarily a desktop app that makes many calls to a web service. We control everything but changes to the overall system design are not going to be seriously considered. One of those calls, Calculate, is used very often, and occasionally can take a few minutes to process all the data to return valid results.
Previously this call to Calculate was done synchronously and thus would block the UI leaving the user to wonder if the app had frozen or not, etc. I've successfully moved all the long wait calls to a BackgroundWorker and then made a simple Waiting screen that would cycle through a "Calculating..." animated message.
Now the problem arises when our UI code tries to call the calculate routine again prior to the first one finishing. I would get a "This BackgroundWorker is currently busy and cannot run multiple instances..." message. Which I thought should be controlled by the resetEvent.WaitOne() calls. It did not so I thought maybe another event controlling access to the entire routine would help, so I added the calcDoneEvent. This still did not fix the problem, but would cause it to block indefinitely on the 2nd call to Calculate's calcDoneEvent.WaitOne() call. Then on a whim I added the Application.DoEvents to the bottom of Calculate and viola, problem solved.
I don't want to leave that .DoEvents in there because I've read it can cause problems that later are very difficult to track down. Is there a better way to handle this situation?
Thanks in advance..
Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)
Public Sub Calculate()
calcDoneEvent.WaitOne() ' will wait if there is already a calculate running.'
calcDoneEvent.Reset()
' setup variables for the background worker'
CalculateBGW.RunWorkerAsync() ' Start the call to calculate'
Dim nMsgState As Integer = 0
' will block until the backgorundWorker is done'
Do While Not resetEvent.WaitOne(200) ' sleep for 200 miliseconds, then update the status window'
Select Case nMsgState
Case 1
PleaseWait(True, vbNull, "Calculating. ")
Case 2
PleaseWait(True, vbNull, "Calculating.. ")
Case 3
PleaseWait(True, vbNull, "Calculating... ")
Case 4
PleaseWait(True, vbNull, "Calculating....")
Case Else
PleaseWait(True, vbNull, "Calculating ")
End Select
nMsgState = (nMsgState + 1) Mod 5
Loop
PleaseWait(False, vbNull) 'make sure the wait screen goes away'
calcDoneEvent.Set() ' allow another calculate to proceed'
Application.DoEvents() ' I hate using this here'
End Sub
Private Sub CalculateBGW_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles CalculateBGW.DoWork
Try
'make WS Call, do data processing on it, can take a long time..'
'No Catch inside the DoWork for BGW, or exception handling wont work right...'
'Catch'
Finally
resetEvent.Set() 'unblock the main thread'
End Try
End Sub
Private Sub CalculateBGW_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles CalculateBGW.RunWorkerCompleted
'If an error occurs we must check e.Error prior to touching e.Result, or the BGW'
'will possibly "eat" the exception for breakfast (I hear theyre tasty w/ jam)'
If Not (e.Error Is Nothing) Then
'If a Web Exception timeout, retry the call'
If TypeOf e.Error Is System.Net.WebException And _
e.Error.Message = "The operation has timed out" And _
intRetryCount < intRetryMax Then
' Code for checking retry times, increasing timeout, then possibly recalling the BGW'
resetEvent.Reset()
CalculateBGW.RunWorkerAsync() 'restart the call to the WS'
Else
Throw e.Error ' after intRetryMax times, go ahead and throw the error up higher'
End If
Else
Try
'normal completion stuff'
Catch ex As Exception
Throw
End Try
End If
End Sub
You declared:
Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)
as private fields of the containing class. Notice that this way, all calls to RunWorkerAsync() are referred to the same object instance of the BackgroundWorker class (that is, to the same object). That is why it is "busy". This code is built to hold only one BackgroundWorker at a given time.
If you mean to allow the UI code to call the Calculate() method whenever it needs to, you should declare CalculateBGW as a local variable within the Calculate() method, thus creating a new instance of the BackgroundWorker class with every call (and they will run asynchronosly). This means you'll have to add and remove the event handlers inside Calculate() as well, using AddHandler and RemoveHandler.
There are several approaches to updating the UI on the progress, but it is suggested to use the BackgroundWorker.ProgressChanged event and BackgroundWorker.ReportProgress method.
Use the BackgroundWorker.RunWorkerCompleted event as a callback trigger, reporting the UI that the calculation is completed, thus triggering the needed code to represent the result. This approach eliminates the need to maintain a thread looping around bossing the calculation thread - thereby eliminating the need for DoEvents(). It lets the calculation thread inform its boss when its done working, instead of having the boss checking the worker's status and going to sleep over and over.