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.
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'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
Given an asynchronous method that does both CPU and IO work such as:
Public Async Function RunAsync() As Task
DoWork()
Await networkStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(False)
End Function
Which of the following options is the best way to call that asynchronous method from Task.Run in Visual Basic and why?
Which is the VB equivalent for C# Task.Run(() => RunAsync())?
Await Task.Run(Function() RunAsync())
' or
Await Task.Run(Sub() RunAsync())
Are the Async/Await keywords within Task.Run necessary or redundant? This comment claims they're redundant, but this answer suggests it might be necessary in certain cases:
Await Task.Run(Async Function()
Await RunAsync()
End Function)
Is ConfigureAwait useful within Task.Run?
Await Task.Run(Function() RunAsync().ConfigureAwait(False))
Await Task.Run(Async Function()
Await RunAsync().ConfigureAwait(False)
End Function)
Which of the above 5 Task.Run options is best practice?
Note: There's a similar question How to call Async Method within Task.Run? but it's for C#, the selected answer has negative votes, and doesn't address ConfigureAwait.
Which is the VB equivalent for C# Task.Run(() => RunAsync())?
My VB is horribly rusty, but it should not be the Sub one, so I'd go with:
Task.Run(Function() RunAsync())
Are the Async/Await keywords within Task.Run necessary or redundant?
I have a blog post on the subject. In this case, they're redundant because the delegate is trivial.
Is ConfigureAwait useful within Task.Run?
Only if you do an Await.
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());
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.