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.
Related
Take this async function:
Public Async Function MyAsyncFunction() As Task(Of String)
Return Await SomeFunctionThatReturnsAString()
End Function
If I call it from a synchronous function like this:
Public Function MySyncFunction() As String
Return MyAsyncFunction.Result
End Function
... the function hangs.
I wondered if it is possible to write an extension like this (the extension below does not work):
Public Module TaskExtensions
<Extension()>
Public Function SyncronousResult(Of T)(aTask As Task(Of T)) As T
Dim result As T = Nothing
Task.Run(Sub()
result = aTask.Result
End Sub).Wait()
Return result
End Function
End Module
... so I could call it like this:
Public Function MySyncFunction() As String
Return MyAsyncFunction.SyncronousResult
End Function
?
The ideal solution is not to block on asynchronous code (as I explain on my blog).
But, if you're in a situation where you literally can't use Await (for some reason), then you can try using one of the techniques in my Brownfield Async article. Note that there is no general-purpose solution that works everywhere.
The one you suggest is blocking for the result in a thread pool thread, which may or may not work. In the example code you posted, it wouldn't work because MyAsyncFunction has already captured the UI context and will attempt to resume in that context.
What you can try is call MyAsyncFunction from inside a delegate passed to Task.Run and then call GetAwaiter().GetResult() on the returned task. That works in most cases. In my article I refer to this as the "Thread Pool Hack".
I am using DSHARPPLUS (for Discord) in my VB.NET application. I've wrapped the messages coming from discord with a handler (which is a async handler):
AddHandler DiscordGuild.MessageCreated, AddressOf MessageRecievedFromDiscord
.. I then use it like so:
Public Async Function MessageRecievedFromDiscord(sender As DiscordClient, e As MessageCreateEventArgs) As Task(Of Boolean)
Await Task.Delay(0)
If e.Author.IsBot Then Return True
If e.Channel.IsPrivate Then Return True
Dim channel As ULong = e.Message.ChannelId
If channel = mainGlobalChannel Then
If e.Message.Content.ToLower().StartsWith("!mute") Then
'Do Stuff here
Return True
End If
End If
End Function
My question is, does this run ASYNC properly? Is this even right? I want the messages to be handled off the main thread obviously. I am not sure if it really is. (Often lately my bot seems to be disconnecting and I wonder if it has to do with the way it's handling messages, it will just send the message using something like DiscordGuild.SendMessageAsync(queueOut.channel, queueOut.message)
which should work anyways.
Thanks!
I'm new to VB and not very familiar with async functions.
I need to fix a bug in existing code where some code is building a report before the data has finished loading.
The problem is that BuildReport() is getting called before the loadOption1(data) has been called. I need the application to wait for all of LoadAsync() to complete before running, but when it waits for GetData(), the application is returning to start() and running BuildReport() too early.
The code looks roughly like this:
Public Async Sub start()
await LoadAsync()
BuildReport() ' this must not run until everything in Load is complete
End Sub
Public Async Function LoadAsync() As System.Threading.Tasks.Task
'this is called from other locations, not just from Start()
dim data = await GetData() 'call to database
' at this point start() continues to run
' but we need it to keep waiting for these next calls to complete
'these calls are synchronous, builds objects needed for the report
loadOption1(data)
loadOption2(data)
loadOption3(data)
'now we want to return to start()
End Function
Public Async Function GetData(s As Scenario) As Task(Of DataResults)
...
Dim resp = Await Task.Run(Function() ConfigWebService.FetchIncentives(req)) ' soap call
End Function
(each function is in separate classes within the same project)
I have tried removing the await from the start function; the options load after BuildReport() is called.
if I remove the await from the GetData() call, passing data.result to the loadoptions functions, the whole application just hangs forever.
I'm stumped. Any tips would be greatly appreciated!
edit: updated the sample to correctly reflect the actual code
Update:
I have tried everything I can think of, from .ContinueWith(False), to attaching to the parent task, to using .ContinueWith(), but nothing has worked so far.
As soon as the code hits the await inside the LoadAsync(), the task in Start() is considered complete
the only thing that seemed to work was to making a isLoading flag that gets set to true at the start of LoadAsync(), then set to false after the LoadOptions3() finished. Then checking if the flag's value has changed in a loop with a delay
Public Async Sub start()
await LoadAsync()
For i As Integer = 1 To 50
If Not IsLoading Then Exit For
Await System.Threading.Tasks.Task.Delay(100)
Next
BuildReport()
End Sub
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());
My question involves async operations in VB .NET.
Given the following:
Delegate WorkerDelegate(Byval asyncOp As AsyncOperation)
Public Sub StartWork()
Dim worker as new WorkerDelegate(AddressOf DoWork)
Dim asyncOp as AsyncOperation = AsyncOperationManager.CreateOperation(New Object)
// begin work on different thread
worker.BeginInvoke(asyncOp, Nothing, Nothing)
End Sub
Private Sub DoWork(Byval asyncOp as AsyncOperation)
// do stuff
// work finished, post
asyncOp.PostOperationCompleted(AddressOf OnDownloadFinished, Nothing)
End Sub
Private Sub OnDownloadFinished()
// Back on the main thread now
End Sub
Most resources I've read say that if you use BeginInvoke on a delegate you must call EndInvoke. In my example above I am using the PostOperationCompleted method to switch threads back and report the operation has finished.
Do I still need to get an IAsyncResult when I call worker.BeginInvoke and add worker.EndInvoke in the OnDownloadFinished method?
It's best practice to call EndInvoke, because that's when resources assigned by the AsyncResult are cleaned.
However, AFAIK the async result used by the asynchronous delegate does not use any resource if you don't access the WaitHandle property, so not calling the EndInvoke may have no impact.
In your scenario, you should consider using ThreadPool.QueueUserWorkItem.
In the example on MSDN, the equivalent of your OnDownloadFinished method looks like this:
// This method is invoked via the AsyncOperation object,
// so it is guaranteed to be executed on the correct thread.
private void CalculateCompleted(object operationState)
{
CalculatePrimeCompletedEventArgs e =
operationState as CalculatePrimeCompletedEventArgs;
OnCalculatePrimeCompleted(e);
}
It does not call EndInvoke(). So it's safe to assume that not calling EndInvoke() in the PostOperationCompleted handler is alright.