Thrown exception stopping application despite being in a try block - vb.net

I have an async function that makes a call to an API, but sometimes there is bad data and I want an exception to be thrown to stop other subsequent procedures from having to run. The Async procedure looks like this:
public async Function getInfo(url as string) as task(of string)
Dim htpRes As HttpResponseMessage = Await url.GetAsync().ConfigureAwait(False)
Dim result = htpRes.Content.ReadAsStringAsync.Result
If result = "" Then
Throw New Exception("API Failed")
Else
Return result
End If
End Function
That function is called by a procedure that looks like this:
sub hitAllAPIs(apiList As List(Of String))
For each i In apiList
Try
Dim info As String = getInfo(i)
doOtherStuffWithInfo(info)
Catch ex As Exception
logError
End Try
Next
End sub
The desired behavior is for the forloop in 'hitAllAPIs' to keep running even if an exception is thrown within 'getInfo'. Instead, what happens is that the exception gets hit and stops the code from running, whether I'm in Debug mode or Release mode. If I'm not there to babysit it and hit 'continue' then the forloop will just stop and the program won't run anymore. Once I hit 'continue', btw, the 'Catch' will work and the error will be logged.
The issue is that I need this to all happen automatically and that's not happening. I can't just eliminate the exception and check the function for a null value, since this is a very simplified version of my code and the function is actually called all over the place. I know that I can change my exception settings to simply skip over all exceptions like this, but this is happening even in Release mode to code that has been deployed. I can't imagine that my debugging exceptions should have an effect on code deployed in Release mode. In any case, I'm hoping someone can help me understand why this exception isn't being automatically handled by the try block.
Thanks!

It seems result = "" is an expected result not an exception. Using Try/Catch is rather heavy handed. Exception handling is for unexpected results. Get rid of the Throw in the Function and add and If in the For Each.
Public Async Function getInfo(url As String) As Task(Of String)
Dim htpRes As HttpResponseMessage = Await url.GetAsync().ConfigureAwait(False)
Dim result = htpRes.Content.ReadAsStringAsync.Result
Return result
End Function
Sub hitAllAPIs(apiList As List(Of String))
For Each i In apiList
Dim info As String = getInfo(i)
If info = "" Then
'Add an overload of logError that accepts a string
logError("API failed")
Else
doOtherStuffWithInfo(info)
End If
Next
End Sub

Related

Exception handling with Async Task functions

I am beginning to learn about Async / Task functions for running cancellable SQL queries in VB.NET.
I have the following code in a class library which runs two tasks and does not have any exception handling, as I would like to handle this in the application that calls the class library. I have the following code in the class library.
Public Async Function DirectoryList(ct As CancellationToken) As Task(Of List(Of Directory))
ct.ThrowIfCancellationRequested()
Dim ds As DataSet
Dim dirs As List(Of Directory)
Dim ctUnregister As CancellationTokenRegistration
ds = Await Task.Run(Function()
Using newConnection = New SqlConnection(Me.InitializationData.ConnectionString)
Using sqlAdapter = New SqlDataAdapter("DirectoryList", newConnection)
ctUnregister = ct.Register(Sub() sqlAdapter.SelectCommand.Cancel())
With sqlAdapter
.SelectCommand.CommandType = CommandType.StoredProcedure
.SelectCommand.CommandTimeout = Me.InitializationData.CommandTimeout
End With
Dim newDataSet As DataSet = New DataSet()
sqlAdapter.Fill(newDataSet)
Return newDataSet
End Using
End Using
' Remove the registration we set earlier so the cancellation token can be used normally.
ctUnregister.Dispose()
End Function, ct)
dirs = Await Task.Run(Function()
Dim dirsResult As New List(Of Directory)
Dim tbl As DataTable = ds.Tables(0)
For Each row As DataRow In tbl.Select()
' process the data
ct.ThrowIfCancellationRequested()
Next
Return dirsResult
End Function, ct)
Return dirs
End Function
I then call it as follows:
Try
dirs = Await databaseLibrary.DirectoryList(cts.Token)
MsgBox("Query complete!")
Catch ex As System.Data.SqlClient.SqlException
MsgBox("Cancelled (SQL)")
Catch ex2 As OperationCanceledException
MsgBox("Cancelled")
Catch ex3 As Exception
MsgBox("Cancelled")
End Try
Functionally it seems to work as expected - I can cancel the requests and exceptions are thrown as expected.
However I would like to handle the exceptions so I can gracefully cancel the tasks, but even when running in Debug mode within the IDE, the app still breaks and the exception (e.g. SqlException) is shown within the IDE. If I step on, eventually the Catch logic runs.
When the application is run outside of the IDE, the exception handling works as expected.
This seems different to the normal behaviour when running in the debugger- normally the exception handling runs but only breaks if the exception is unhandled.
Why is this behaviour different, presumably because of the Async functions?
when running in Debug mode within the IDE, the app still breaks and the exception (e.g. SqlException) is shown within the IDE.
Why is this behaviour different, presumably because of the Async functions?
Exceptions thrown by asynchronous functions are not caught directly by the catch. What actually happens is that the exception is caught by a compiler-generated state machine, and that exception is placed on the returned Task. Later, when that task is awaited, the exception is re-raised, and then can be caught by the catch.
However, this "indirection" in exception handling means that the IDE does kind of freak out when the exception is initially thrown. As far as it can tell, there is no catch in your code that is going to catch it, so that's why it breaks and displays the exception. It doesn't know that the compiler-generated code will catch it. You can tell the IDE not to worry about uncaught exceptions.

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

Update a label from a task

I'm trying to implement tasks in my program. I launch a task that will produce a log file, and after, I want to update the label to say "Log sucessfully saved".
Here is my code
Private Function Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Try
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Catch ex As Exception
End Try
Return 1
End Function
Private Function UpdateLabel(ByVal text As String)
Label1.Text = text
Return 1
End Function
I launch the task from the Main form in the Load() :
Dim tasktest = Task(Of Integer).Factory.StartNew(Function() Createlog(theList))
(I don't know if it is better to use the factory or declare as a task and then task.Start())
I have the error on the label update :
Cross-thread operation not valid: Control 'Label1' accessed from a thread
other than the thread it was created on.
Could you please explain why it doesn't work with the invoke method ? And do you have an alternative solution ?
Thanks for your help
First, UpdateLabel should be a Sub, not a Function. Second, this line is wrong:
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Read it again. You are, in order, executing the UpdateLabel function, then passing the result of that function to Me.Invoke (if you used Sub instead of Function, the compiler should have warned you about the error).
This doesn't raise any compiler errors because a Function declared without As [Type] is defaulted to As Object, that can be cast to anything. It should be:
Me.Invoke(Sub()
UpdateLabel("Log sucessfully saved")
End Sub)
To simplify, your code can be rewritten like this:
Private Sub Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Me.Invoke(Sub()
Label1.Text = "Log sucessfully saved"
End Sub)
End Sub

ContinueWith after PostAsJsonAsyc

I've got a vs2010, 4.0 vb.net, WinForms app calling AttemptLogin on a form load event.
I want to avoid blocking the form load if possible and I was hoping the tasks and continuation stuff in 4.0 would be the right way to go as I could run the main task on the default scheduler and the continuation on fromcurrentsynchronisationcontext but although I've got the OnAttemptLogin working I can't get my OnAttemptLoginCompleted function in the continuation to be called.
I think it's because the OnAttemptLogin returns a "RunToCompletion" task so the continuation never gets called. But I don't know how to deal with that, I've tried numerous things but I've now confused myself so much I'm pretty much mashing keys. Can anyone offer any advice? Am I simply doing it wrong or have I got the wrong idea all together?
Here's what I have so far, the OnAttemptLogin works as I would expect it to, but it then never calls the LongRunning tasks continuation.
Please note: I can't use await as I'm in vs2010 .net4.0 so I'm stuck with ContinueWith.
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Try
Dim LongRunningTask As Task(Of HttpResponseMessage) = Task.Factory.StartNew(Function()
Return OnAttemptLogin()
End Function, TaskScheduler.Default)
Dim UITask As Task(Of HttpResponseMessage) = LongRunningTask.ContinueWith(Sub(t)
OnAttemptLoginCompleted(t)
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
LongRunningTask.Wait()
Catch ex As AggregateException
' nom nom nom
' do something useful
End Try
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
The problem here is ... convoluted. The main issue you have here, the reason why UITask won't run, is because LongRunningTask is not of type Task(Of HttpResponseMessage). It is actually a Task(Of Task(Of HttpResponseMessage)). OnAttempLogin() returns a Task(of H...), but the task that you start in form load is a Task that will return that Task, hence, Task(Of Task(Of ...)). So there's an exception in that line, hence the UITask line never runs. So the problem with the code is that there's too many Task things all over the place.
The other problem is that you aren't really doing anything asynchronously (except that part that never ran) since you are Wait()-ing for all the tasks. So you need to get rid of most of your waits to actually achieve that. Getting rid of the waits means you need to handle exceptions with a continuation.
Some minor points:
You don't really need the scheduler stuff, either.
UITask is simply a Task, not a Task(Of ...) since it doesn't return anything.
I'm continuing from UITask to handle exceptions so that it also catches UITask's exceptions. If I continued from LongRunningTask, I would miss those exceptions.
Below is an example of what I think the new code will look like. There may be a few syntax issues since I'm missing a few things to get this to compile:
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Dim LongRunningTask As Task(Of HttpResponseMessage) = OnAttemptLogin()
Dim UITask As Task = LongRunningTask.ContinueWith(AddressOf OnAttemptLoginCompleted)
uiTask.ContinueWith(Sub(t)
Dim ex As AggregateException = t.Exception
'nom nom nom
'all your exceptions will end up here.
End Sub, TaskContinuationOptions.OnlyOnFaulted)
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
my solution was to delete everything and give up, i will use something else, anything else, pff at this point ill lock the ui and not care, three days on this rubbish is crazy.
Marking jtseng's reply as correct even though it didnt work as hes the only reply and deserves something for taking the time to try and help.

Catch, Handle, then Rethrow Exception?

I ran into an interesting dilemma today. I have a function that handles information and checks for duplicate values, then returns the next number that is not a duplicate. So, I have something like this:
Public Function GetNextNonDuplicateNumber(NumberToCheck as Long) as Long
//the non-duplicate the function will return
Dim NonDuplicate as Long
If CheckForDuplicate(NumberToCheck) = True Then
Throw New DuplicateException()
Else
NonDuplicate = NumberToCheck
End If
End Function
Then at the bottom of the function I have a catch block that handles the duplicate by incrementing until I don't have a duplicate any more, like this:
Catch ex as DuplicateException
NonDuplicate = IncrementToNonDuplicateValue(NumberToCheck)
Throw ex
Return NonDuplicate
End Function
As you can see, I want to handle the exception specifically, but I also want to throw it when I'm done because I want to alert other code outside the function.
The problem is that simply throwing it exits out of the function with a null value. Am I thinking about a try/catch the wrong way, or is there a way around this?
If you caught an exception and recovered from it (with your IncrementToNonDuplicate...) then there is no reason to throw an exception anymore. Code between catch and end try should just clean the resources like closing a file or datareader if you will rethrow it.
You could rather return a structure that contains NonDuplicate value and required information about errors in function.
Other way would be to throw a custom exception that will contain information like "Invalid number: it should be...)
You can return a boolean indicating if a duplicate is found, and change the parameter to be passed in by reference so you can update the value.
Public Function GetNextNonDuplicateNumber(ByRef NonDupeNumber as Long) as Boolean