vb.net Not waiting until all tasks have completed - vb.net

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.

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.

Looped Tasks Submitted Inside Try/Catch Blocks

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.

Async/ConfigureAwait(True) does not wait - WebService

I have a webservice GetCard which calls a function CreateRaces. Inside the CreateRaces I am saving the new objects. This works fine without async. However, when I call the async function for task1 CommitAsync the method executes and returns a response without hitting the next break point. I have tried to work around with via Task.Run, ConfigureWait(true), and with
With HttpContext.Current
bulk.Setup etc...
End With
but nothing seems to work.
The bulk.Setup - CommitAsync calls a third party dll (SqlBulkTools) which returns Task.
It looks like it works on a different thread.
Any ideas how to configure it so I the function executes and I can hit the next break point which is sw.Stop()?
<HttpGet>
<AllowAnonymous>
Public Async Function GetCard(cardurl As String) As HttpResponseMessage
Try
Dim oRace As New RaceCard(cardurl)
oRace.DoWork()
Dim oRC As New RaceController
await oRC.CreateRaces(oRace.Races)
Return Request.CreateResponse(HttpStatusCode.OK, True.ToJson)
Catch ex As Exception
Return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex)
End Try
End Function
CreateRaces function
Public Async Function CreateRaces(ByVal t As List(Of Race)) As Threading.Tasks.Task(Of Boolean)
Try
'Proceed only if list has items
If t.Count > 0 Then
Dim bulk As New BulkOperations()
Dim sw As New Stopwatch()
Dim oCon As New SqlClient.SqlConnection(DotNetNuke.Common.Utilities.Config.GetConnectionString)
sw.Start()
Dim task1 = Await bulk.Setup(Of Race).ForCollection(t).WithTable("ArbiRace").AddAllColumns().BulkInsertOrUpdate().SetIdentityColumn(Function(i) i.RaceID).MatchTargetOn(Function(m) m.RaceName).CommitAsync(oCon).ConfigureAwait(True)
sw.Stop()
Dim swt = sw.ElapsedMilliseconds
End If
Return True
Catch ex As Exception
Dim s As String
s = ex.ToString
Return False
End Try
End Function
Asnyc calls can be handled in 2 ways:
Deliberately halting thread execution until one, several, or all tasks are completed.
Leaving the task to run while the current thread goes to do something else.
Based on your question, you are expecting #1, but your code currently does #2. Await Documentation:
An Await expression or statement does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method, after the Await expression, as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.
In your case, once CreateRaces() reaches the Dim task1 line (which will eventually store the result, not the task), control returns to GetCard(). However, the CreateRaces() call here also uses an Await, so control attempts to go back up the call stack again. Except, there is nowhere left to go up, because you are at the original endpoint. This is why you seem to get a response too soon.
Since you are waiting for the task to finish to get the elapsed time, you want to wait for the tasks to finish before returning a response. This can be accomplished with task.Result, task.Wait(), or the shared method Task.WaitAll().
Example:
<HttpGet>
<AllowAnonymous>
Public Function GetCard(ByVal cardUrl As String) As HttpResponseMessage
Try
Dim oRace As New RaceCard(cardurl)
oRace.DoWork()
Dim oRC As New RaceController
Dim createRacesTask As Threading.Tasks.Task(Of Boolean) = oRC.CreateRaces(oRace.Races)
'Do other tasks here while we wait
Threading.Tasks.Task.WaitAll(createRacesTask, ...)
Return Request.CreateResponse(HttpStatusCode.OK, True.ToJson)
Catch ex As Exception
Return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex)
End Try
End Function

Threading: Invoke gets stuck .NET

I have a system tray application. The tray application has an icon and a context menu with some options. There is a menu called status which contains below toolstripmenuitems:
Start
Restart
Stop
They are enabled/disabled according to some conditions.
My system tray application, has a background thread does continuously check some conditions and do some work. Its main loop is below:
Do Until gAppExit Or Me.thdExit
' Check some conditions and do some work
Loop
gAppExit is a global variable that indicates whether user has exited from application through the 'exit' toolstripmenuitem.
thdExit indicates whether the thread should exit from the loop (I explain it later).
When user want to restart the background thread, he clicks on restart toolstripmenuitem and below sequence is done (Restart -> Halt -> WaitFinish):
Public Function ReStart() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
result = Me.Halt()
If result <> RESULT_ERROR Then
result = Me.Start()
End If
Return result
End Function
Public Function Halt() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
Me.thdExit = True
result = Me.WaitFinish()
Return result
End Function
Public Function WaitFinish() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Return RESULT_ERROR
End If
result = RESULT_ERROR
Try
'TODO:
Console.WriteLine("Wait thread to finish (before Join)...")
Me.ThreadBgWorker.Join()
'TODO:
Console.WriteLine("Thread finished... Continue restarting thread...")
Me.RetVal = True
result = Me.ThreadBgWorker.ManagedThreadId
Logger.Write(String.Format("Task ""{0}"" correctly stopped.", _
Me.ThreadBgWorker.Name))
Catch ex As Exception
Logger.Write(String.Format("Couldn't stop task ""{0}"": {1}", _
Me.ThreadBgWorker.Name, ex.Message), _
LOGGING_CRITICAL_ERRORS_CATEGORY)
End Try
Return result
End Function
Public Function Start() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
result = RESULT_ERROR
Me.thdExit = False
Try
If Me.ThreadBgWorker.ThreadState = Threading.ThreadState.Stopped Then
Me.Create()
End If
Me.ThreadBgWorker.Start() ' Start the new thread.
result = Me.ThreadBgWorker.ManagedThreadId
Logger.Write(String.Format("Task ""{0}"" correctly started.", _
Me.ThreadBgWorker.Name))
Catch ex As Exception
Logger.Write(String.Format("Couldn't start task ""{0}"": {1}", _
Me.ThreadBgWorker.Name, ex.Message), _
LOGGING_CRITICAL_ERRORS_CATEGORY)
End Try
Return result
End Function
Note that on Halt function, it awaits thread to finish by calling Me.ThreadBgWorker.Join() on function WaitFinish. Before calling WaitFinish function, thdExit is set to true in order to background thread can exit from main loop:
Do Until gAppExit Or Me.thdExit
' Check some conditions and do some work
Loop
ChangeStatusToStopped()
on exit loop, ChangeStatusToStopped() is called, and it is as below:
Private Delegate Sub ChangeStatusToStoppedDelegate()
Public Sub ChangeStatusToStopped()
' TODO:
Console.WriteLine("Changing status to stopped...")
System.Windows.Forms.Application.DoEvents()
If MainMenu.InvokeRequired Then
'TODO:
Console.WriteLine("Invoke required!")
MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))
Else
'TODO:
Console.WriteLine("Invoke NOT required!")
Me.submnuStart.Enabled = True
Me.submnuReStart.Enabled = False
Me.submnuStop.Enabled = False
End If
' TODO:
Console.WriteLine("Status changed to stopped.")
End Sub
What it does is to enable Start toolstripmenuitem and disable restart and stop toolstripmenuitems in the UI.
The problem is:
Within ChangeStatusToStopped method, when MainMenu.InvokeRequired is true, it calls:
MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))
and then it gets stuck there, that is, else body:
Me.submnuStart.Enabled = True
Me.submnuReStart.Enabled = False
Me.submnuStop.Enabled = False
is never executed. It seems like main thread is busy or some other problem in message pump.... Any ideas?
I have seen that line:
Me.ThreadBgWorker.Join()
in WaitFinish() function is reached before background thread exits main loop and despite thdExit has been set to true before doing Me.ThreadBgWorker.Join(), once join is performed, application gets stuck, background thread cannot exit main loop (seems application is busy or frozen).

VB.Net threading advice

I have datagrid with some data and I'm making a loop through the items with for... next
Inside the loop i'm making a call to a web service with parameters from the datagrid
Because of the speed the work is very time consuming and I want to give the user the option to select how many multiple calls to the service he want.
How can I make simultaneously calls to the web service inside the loop?
There are many ways to achieve what you want so here's an example using the Task class. The key point is to iterate the underlying data source (read only) inside the background thread. When finished, move back to the UI thread and update.
Private Sub BeginAsyncOp(list As IList)
Static cachedSource As CancellationTokenSource
Static cachedId As Long
If ((Not cachedSource Is Nothing) AndAlso (Not cachedSource.IsCancellationRequested)) Then
cachedSource.Cancel()
End If
cachedSource = New CancellationTokenSource
cachedId += 1L
Dim token As CancellationToken = cachedSource.Token
Dim id As Integer = cachedId
Task.Factory.StartNew(
Sub()
Dim result As IList = Nothing
Dim [error] As Exception = Nothing
Dim cancelled As Boolean = False
Try
'Background thread, do not make any UI calls.
For Each item In list
'...
token.ThrowIfCancellationRequested(True)
Next
result = a_updated_list
Catch ex As OperationCanceledException
cancelled = True
Catch ex As ObjectDisposedException
cancelled = True
Catch ex As Exception
[error] = ex
Finally
If (id = cachedId) Then
Me.Invoke(
Sub()
If (((Not Me.IsDisposed) AndAlso (Not Me.Disposing))) Then
'UI thread.
If (Not [error] Is Nothing) Then
'...
ElseIf (Not cancelled) Then
For Each item In result
'...
Next
End If
End If
End Sub)
End If
End Try
End Sub)
End Sub