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
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".
Is there a way to await the calling task?
Async Function DoStuff() As Task
StartDoingOtherStuff()
' Doing stuff
End Function
Async Function StartDoingOtherStuff() As Task
' Doing stuff
Await callingTask
' Finish up
End Function
Note: I want to paralize the task, because it involves uploading a file to multiple destinations. But I want to await the calling task, to delete the file after all uploads are complete.
Based on usr's answer to Get the current Task instance in an async method body, you can do this:
Private Async Function DoStuff() As Task
'Capture the resulting task in a variable.
Dim t As Task = (
Async Function() As Task
Console.WriteLine("DoStuff()")
'First await. Required in order to return the task to 't'.
Await Task.Delay(1)
'Disable warnings:
' "Because this call is not awaited (...)"
' "Variable 't' is used before it has been assigned a value (...)"
#Disable Warning BC42358, BC42104
'Call other method.
DoOtherStuff(t)
#Enable Warning BC42358, BC42104
'Simulate process.
Await Task.Delay(3000)
End Function
).Invoke()
'Await if needed.
Await t
End Function
Private Async Function DoOtherStuff(ByVal ParentTask As Task) As Task
Console.WriteLine("DoOtherStuff()")
'Await parent task.
Await ParentTask
Console.WriteLine("DoStuff() finished!")
End Function
By using a lambda expression you can capture the current task and then pass it to itself. Await Task.Delay(1) is required in order for the async method to return its task so that it can be set to the variable. However, you can remove it if you already have another await before DoOtherStuff() is called.
I've read a lot of tutorials with regards to await/async and i've understood that when it comes to await keyword it will go back to the main context then go back to await (when it's finished) and then continue further if there's something after away within async method. I also read that there is something like ConfigureAwait(False) which simply means that with opposite to i've written before when it comes to await it will not go back to main context at this point but will stay here and wll wait on await to be finished and then continue in async method after that will go back to main cotext. So diffrence is it will not go back to main context but wait on await. Correct if i am wrong. Based on that knowledge in first example i should see following result:
After DoStafAsync
Done running the long operation asynchronously.
but when i just simply add ConfigureAwait(False) it should be:
Done running the long operation asynchronously.
After DoStafAsync
as it will not go back to context but will wait and after async method finishes then will go back. Somehow i see the result as in first output. What i am missing?
Public Class Form1
Sub LongOperation()
Threading.Thread.Sleep(5000)
End Sub
Private Async Function DoStafAsync() As Task
lblStatus.Text = "Running a long operation asynchronously... (UI thread should be fully responsive)"
Await Task.Run(Sub()
LongOperation()
End Sub).ConfigureAwait(False)
lblStatus.Text = "Done running the long operation asynchronously."
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dfg
lblStatus.Text = "After DoStafAsync"
End Sub
Async Sub Dfg
Await DoStafAsync
End Sub
End Class
Additional question: Some people claims that tasks are not creating
threads but working on same ui thread. But there are some people
claiming that they are and there are also people claiming that
sometimes tasks creating threads and sometimes not. What is true here?
i've understood that when it comes to await keyword it will go back to the main context then go back to await (when it's finished) and then continue further if there's something after away within async method.
I don't think you're using the term "context" the way it's normally used when explaining async code. Check out my async intro blog post, which explains exactly what the context is and how it's used by await.
In particular, "context" does not mean "method or function". It does not determine how or when the code returns to another part of the code.
You may also want to read my MSDN article on async best practices, which explains why you should avoid async void (except for event handlers). If you make Form1_Load an async void and get rid of dfg, then it works as you would expect:
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await DoStafAsync
lblStatus.Text = "After DoStafAsync"
End Sub
The reason it wasn't working before is because Form1_Load was not waiting for dfg (or DoStafAsync) to complete - it was just firing them off and then proceeding immediately to setting the label text. As I point out in my best practices article, one of the main problems with async void (async sub) is that the calling code cannot determine when it has completed (at least, not easily).
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 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.