Task.Run - Handling exceptions - vb.net

I am having a function as below (kept only relevant code for this example):
Public Async Function RefreshCompanyCodesAsync() As Task(Of Boolean)
For ctr=1 to 2000
Await Task.Delay(1000).ConfigureAwait(False)
If ctr=5 Then Throw New ApplicationException("Why 5?")
Next
return true
End Function
As you can see, I am generating an exception when ctr = 5 just to check how the exception will be handled in the UI.
I then call this function inside a button click Async event:
Dim task1 = Task.Run(Sub()
RefreshCompanyCodesAsync()
End Sub)
Dim cTask = task1.ContinueWith(Sub(antecedent)
If task1.Status = TaskStatus.RanToCompletion Then
MsgBox("All done")
ElseIf task1.Status = TaskStatus.Faulted Then
MsgBox(task1.Exception.GetBaseException().Message)
End If
End Sub)
The task runs to completion instead of being faulted. I am guessing thats because a wrapper task runs to completion instead of the original task that threw an exception. However, am not sure how to get to the original task and handle the exception.
I have tried with Await.
Try
Await RefreshCompanyCodesAsync()
Catch ax As AggregateException
MsgBox(ax.Message)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Exceptions get caught here, but the problem is I need to start multiple functionalities in parallel of which RefreshCompanyCodesAsync is only one of them. To do that, I believe launching those functionalities via Task.Run would be the best bet.
The actual functionality of the worker function in my case involves extensive DB and HttpClient activity and during this time, the UI responsiveness is driving me towards going in for Task.Run or Factory.StartNew
Also, have tried launching the task and Waiting for it so as to ensure evaluation of the result. However, my UI gets blocked when I do Task.Wait.
Any help is really appreciated.

Probably, what you want is
Dim task1 = RefreshCompanyCodesAsync()
That way there is no wrapper task. RefreshCompanyCodesAsync already is non-blocking which is important so that you don't freeze the UI.
You should avoid ContinueWith. Prefer await because it marshals you back to the UI thread.
I'll need to write this in C#:
var task1 = RefreshCompanyCodesAsync();
await Task.WhenAny(task1); //Wait without throwing.
MessageBox.Show(task1.Status.ToString());

Related

Throwing a custom exception in Async sub is not handled in a Try Catch

I noticed something strange and cannot find why this wouldn't work correctly.
The error i'm experiencing is in a Async sub where a exception is thrown:
Private Async Sub Test1
If Await SomeAsyncReadProcess <> "My Result" then
Throw New Exception("Onoo invalid result!")
End if
End sub
Then Higher up the code if have this Sub in a Try Catch. Now the strange thing is then when i Throw the new exception my debugger stops and says System.Reflection.TargetInvocationException. When i revert the sub back to a SYNC sub without the Aynsc/Await then everything works as it should.
You cannot easily catch exceptions from an Async Sub. This is by design, as Async Sub methods are very unusual Async methods that act like top-level event handlers. The general rule is "avoid async void" (or in this case, Async Sub).
The appropriate fix is to change Async Sub to Async Function ... As Task.

UWP (VB.Net) StreamSocketListener not firing connected event

I'm trying to setup a socket listener in a Universal Windows Platform App. The app will run on a Raspberry PI and listen for communications from an in-motion scale.
I've setup a StreamSocketListener in my mainVM file. I've got commands to start and stop it. I've followed the few guides online i can find, but it won't fire the event when the scale sends the data. I've tested with a listener app that I downloaded, so i can confirm that the messages are being sent and my computer is able to receive them (no firewall issues). (I also know that only one app can listen to a port, so this tester is off when testing my program.)
I'll get one hit on the handler function when it first starts the connection, but i never get anything again. No errors, just nothing happening.
Commands:
Public Property cmdStart As New Command(Async Sub() Await StartListener(), True)
Public Property cmdStop As New Command(Sub() StopListener(), True)
Command Subroutines:
Private Async Function StopListener() As Task
If Connected Then
Await _prePriceListener.CancelIOAsync()
RemoveHandler _prePriceListener.ConnectionReceived, Nothing
_prePriceListener.Dispose()
Connected = False
End If
End Function
Private Async Function StartListener() As Task
If ValidateInput() Then
Try
_prePriceListener = New StreamSocketListener()
_prePriceListener.Control.KeepAlive = False
AddHandler _prePriceListener.ConnectionReceived, AddressOf HandlerReceived
Await _prePriceListener.BindServiceNameAsync("6021")
Connected = True
Catch ex As Exception
Message = ex.Message
End Try
End If
End Function
Event Handler:
Private Async Function HandlerReceived(sender As StreamSocketListener, args As StreamSocketListenerConnectionReceivedEventArgs) As Task
Dim msgReceived As String = ""
Dim inStream As Stream
Dim reader As StreamReader
Try
inStream = args.Socket.InputStream.AsStreamForRead()
reader = New StreamReader(inStream)
Catch ex As Exception
Return
End Try
Try
If reader IsNot Nothing Then
msgReceived = Await reader.ReadLineAsync()
HandlePrepriceRead(msgReceived)
End If
Catch ex As Exception
Return
End Try
End Function
I've tried with _prePriceListener.Control.KeepAlive set to both true and false. When true, it hangs on msgReceived = Await reader.ReadLineAsync() in the handler. I suspect that is the issue, but every fix i've found made no difference. My only other suspicion is incorrect use of Async/Await but i can't find much information to either confirm or deny that. (I think i know what i'm doing with that, but i may be at that level of knowing just enough to be dangerous...)
Anyway, any input would be greatly appreciated. Thanks.
Are you sure that the scale is sending an EOL (\n or \r or \r\n) HEX 13 and or 10 ? I could see your code waiting for an something the scale is never sending. *FYI I'm not sure what kind of scale you are using or how quickly it talks or your application but if the scale is on continuous feed this probably isn't going to work very well for you as the scale and all the parts along the way will buffer things and the weight will be gone by the time you process this event (after a while atleast)

Detecting errors in tasks and reviving

I need to run, lets say, 20 parallel tasks. The task function that is being called is a long running function and can generate errors. I wanted to detect that from the caller and revive the task.
While I understand that handling the exception "inside the task" and rerunning the task is a solution, I was also thinking if there are means to detect this from the caller itself and run the failing task again. When I revive the task it should fall under the bucket of 20 tasks running so that the calling process continues to wait for all the tasks running including the new revived task via Await Task.WhenAll(tasks)
Here is the code on the caller side that creates 20 different task and waits for all of them to finish.
Try
watch = New Stopwatch
watch.Start()
cs = New CancellationTokenSource
Timer1.Enabled = True
Dim tasks = Enumerable.Range(1, 20).Select(Function(i) Task.Run(Function() GetAndProcessMessagesAsync(cs.Token)))
Await Task.WhenAll(tasks)
Catch ex As Exception
MsgBox(ex.Message)
Timer1.Enabled = False
End Try
PS: I have checked the below links for a solution but did not find something suitable that I could try. But definitely would think another method could be to use the TaskContinuationOptions option but am really not sure how and where that would fit in my code above.
Task Parallel Library - How do you get a Continuation with TaskContinuationOptions.OnlyOnCanceled to Fire?
catching parallel tasks if they stop prematurely
I think that doing it inside the Task is the best option, but that doesn't mean you have to change GetAndProcessMessagesAsync. The code could look like this:
Async Function RetryAsync(func As Func(Of Task)) As Task
While True
Try
Await func()
Catch
' TODO: don't catch all exceptions all the time
End Try
End While
End Function
…
Dim tasks = Enumerable.Range(1, 20).Select(
Function(i) Task.Run(
Function() RetryAsync(
Function() GetAndProcessMessagesAsync(cs.Token))))
Also, if GetAndProcessMessagesAsync is truly asynchronous, you might not need that Task.Run.

UI freezes when using Async-await

I have got this function which freezes the UI:
Public Sub ValidateUrlContentAsync(externalApplyURL As String)
AsyncManager.OutstandingOperations.Increment()
Dim result = Threading.Tasks.Task.
Run( _
Async Function()
Return Await ValidateLinks.ValidateUrlContentAsync(externalApplyURL)
End Function).
ContinueWith(
Sub(t)
Try
If t.IsFaulted Then
Throw t.Exception
End If
If t.Result.Error IsNot Nothing Then
ErrorHandler.Log.Warn(
String.Format("ValidationError: {0}, Exception: {1}", externalApplyURL, t.Result.Error))
End If
AsyncManager.Parameters("validationResult") = t.Result.ResultValue.ToString()
Finally
AsyncManager.OutstandingOperations.Decrement()
End Try
End Sub)
End Sub
Public Function ValidateUrlContentCompleted(validationResult As String) As ActionResult
Return Json(validationResult, JsonRequestBehavior.AllowGet)
End Function
I thought that task.run solves this issue because it creates a new thread separated from the UI thread, What's wrong with this code?
One thing that stands out to me is the use of three separate asynchronous code patterns (Await, ContinueWith, and AsyncManager) instead of just using Await.
The other major thing is that you're returning an ActionResult - indicating this is an ASP.NET application - and yet talking about a "UI thread". There is no UI thread on ASP.NET.
Thus, I reinterpret "freezes the UI" as "does not return a result until the handler is complete", which is exactly how ASP.NET is supposed to work.
So, first, remove all the unnecessary AsyncManager, ContinueWith, and Task.Run code, which really simplifies the method:
Public Function ValidateUrlContent(externalApplyURL As String) As Task(Of ActionResult)
Dim result = Await ValidateLinks.ValidateUrlContentAsync(externalApplyURL)
If result.Error IsNot Nothing Then
ErrorHandler.Log.Warn(String.Format("ValidationError: {0}, Exception: {1}", externalApplyURL, result.Error))
End If
Return Json(result.ResultValue.ToString(), JsonRequestBehavior.AllowGet)
End Function
Next, solve the "freezing the UI" problem. The proper place to solve this problem is in the UI, not on the server (ASP.NET) side. The way to prevent the UI from freezing is to call the server in an asynchronous way. If you UI is a .NET application, you can use Await with HttpClient to call it asynchronously.
You must Await the call to ValidateUrlContentAsnc. Turn it into a Function and return Task.

How to call async method asynchronously from non-async method

How do you wait on a task from an async method, in a non-async method, without actually calling the method?
Class TestAsync
Dim workevent As New Threading.ManualResetEvent(False)
Sub work()
Dim Task As Task = Test()
'Do Work which affects Test Here
Threading.Thread.Sleep(100)
workevent.Set()
'wait for Test to complete
'Task.Wait() -causes the application to hang
End Sub
Async Function Test() As Task
workevent.WaitOne()
End Function
End Class
First, if possible, never do that. Synchronously waiting for an async method defeats the whole purpose of using async. Instead you should make work() into an Async Function WorkAsync() As Task. And then make the method(s) that call work() asynchronous too, and so on, all the way up.
Second, asynchronous isn't the same as parallel. If you have Async method with no Awaits, it will execute completely synchronously, as if it was a normal method. So, your method will hang even without Task.Wait().
What you can do is to run Test() on a background thread using Task.Run():
Sub work()
Dim Task As Task = Task.Run(AddressOf Test)
Threading.Thread.Sleep(100)
workevent.Set()
Task.Wait()
End Sub
The Task returned from Run() will complete when the Task returned from Test() completes.
(There are other issues causing hangs when combining Wait() and Async, but they are not relevant in this specific case.)
But I repeat: you shouldn't do this, since it blocks the UI thread, though it's only for a short while.