How to call async method asynchronously from non-async method - vb.net

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.

Related

VB Await an asyc call that also awaits a call

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

Task's ConfigureAwait(False) not working

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).

Task.Run - Handling exceptions

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());

parallel event update UI

I have a class written in C#. In it I want to run a certain function in parallel on a list. After it completes on each item I would like to update a progress bar. However, I get very odd behavior from my program. It executes the event and reaches my sub but never proceeds to actually execute any code. Instead it just freezes. (I've mixed vb.net and c#. It will be rewritten at some point)
so in my windows form I call
progressBar.Visible = True
progressBar.Value = 0
progressBar.Maximum = dataGrid.SelectedRows.Count
AddHandler quoteManager.refreshStarted, AddressOf progressBarCounter
quoteManager.refreshAllAsync(list)
and the event is simply
Private Sub progressBarCounter()
Me.Invoke(Sub()
If progressBar.Value = progressBar.Maximum Then
progressBar.Visible = False
Else
progressBar.Value += 1
End If
End Sub)
End Sub
and in the quote manager class I have this defined.
public event Action refreshStarted;
public void refreshAllAsync(List<BindableQuote> bindableQuotes)
{
bindableQuotes.AsParallel()
.WithDegreeOfParallelism(10)
.ForAll((quote) => {
quote.refreshAll();
if (refreshStarted != null) { refreshStarted(); }
});
}
So for some reason I get it to enter progressBarCounter on each item in the list but it never exists. Instead it just keeps the form frozen.
I am not exactly sure this is what is happening, but it looks like progressBarCounter is blocking because you are calling Invoke. Should you be using BeginInvoke instead? Using BeginInvoke might solve the deadlock issue. See this post: What's the difference between Invoke() and BeginInvoke()
What appears to be happening here is that you access UI objects from multiple threads.
That's not supported. You'll have to run this code on a worker thread, and let it somehow accumulate progress, and send messages back to the UI thread. The BackgroundWorker class can help you implement the marshalling back to the UI thread.

.NET End Invoke / Post Operation Completed

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.