user3967841
user3967841

Reputation:

Async/await pair in visual basic not working as it should?

So, I have the following layout in a part of my program written in Visual Basic 2015: (the following is just pseudocode to make it easier to understand)

Async Button_Click_Event_Handler {
   ... (Synchronous code)
   Await DoWork()
}

Async Sub DoWork() {
   ... (Synchronous Code)
   Dim InitDB_t = Task.Run(Sub() InitDatabase())
   ... (Both synchronous and awaited code)
   Await InitDB_t
   ... (Both synchronous and awaited code)
}

Async InitDatabase() {
   ... (Synchronous Code)
   Await SQLConnection.OpenAsync()
   ... (Synchronous Code)
}

Now, what I wanted to accomplish is the following flow:

Button_Click_Event_Handler ---> DoWork() ---> Start executing InitDatabase(),
while other things that don't depend on the Database get executed --->
After the call "Await InitDB_t" be ***100% SURE*** that the database has been initialized,
i.e. InitDatabase() task has been completed, so that things that depend on it get executed.

Apparently, it seems that VB doesn't respect my flow, or I simply don't understand exactly how Await works, because every single time I run it, I get an exception below Await InitDB_t when I actually use the - assumed initialized - DB and upon checking, I realized to my surprise that InitDatabase() hasn't actually finished, but is still "awaiting" on SQLConnection.OpenAsync(), even though Await InitDB_t has returned!!!

How can I alter my code, without getting a deadlock, since using Task.Wait() would actually block the current execution thread (and thus, I would be 100% sure that anything after it is executed after the DB has been fully initialized, but, oops, I would get a deadlock) and since I need contexts to update some GUI elements?

Edit 1: OK, after further debugging it seems that my logic is more wrong than I expected, since another exception arises: When I execute InitDatabase() I do some GUI assignments too, such as label_p.Content = "finished" which throws an exception because I am in a different thread than the GUI one. So, I would like corrections for both exceptions now...

Upvotes: 1

Views: 2473

Answers (3)

Baskar PC
Baskar PC

Reputation: 189

Try using like below

Await DoWork()..GetAwaiter().GetResult()

You can use this code inside any Non Async Function or Sub to call Async methods.

Refer to the documentation

Upvotes: 0

Fabio
Fabio

Reputation: 32453

Your main problem is that you wrapped InitDatabase() with new thread. Task returned by Task.Run will completes before OpenAsync completes.

Dim InitDB_t = Task.Run(Sub() InitDatabase())

If InitDatabase is properly created asynchronous method (you didn't show full context) then it will return Task.

 Private Async Function InitDatabase() As Task
     ' ... (Synchronous Code)
     Await SQLConnection.OpenAsync()
     ' ... (Synchronous Code)
 End Function   

And Task will be returned back to caller on Await SQLConnection.OpenAsync() line, when OpenAsync completes execution continue to the next line.

So inside Task.Run this method will return value before OpenAsync completes.
Because method InitDatabase completes execution the status of task InitDB_t will be RanToCompletion too while actually OpenAsync not finished yet.

Because all your method are asynchronous you don't need Task.Run at all and all operations can be executed on same thread - which will successfully updates UI controls.

First be sure that InitDatabase return Task. I suggest to rename method to InitDatabaseAsync, suffix "Async" is added by conventions, but I found it very usefull.

 Private Async Function InitDatabaseAsync() As Task
     ' ... (Synchronous Code)
     Await SQLConnection.OpenAsync()
     Me.MyLabel.Text = "Finished"
     ' ... (Synchronous Code)
 End Function   

Then same approach with DoWork method - always return Task with Async method. Exception is UI eventhandlers - with them you can use Async Sub.
If you not returning a Task then your method will work like "Fire and Forget", but you will not notice any exception thrown inside those methods.

 Private Async Function DoWorkAsync() As Task
     ' ... (Synchronous Code)
     Dim InitDB_t = InitDatabaseAsync()
     ' ... (Both synchronous and awaited code)
     Await InitDB_t
     ' ... (Both synchronous and awaited code)
 End Function   

Then in eventhandler

Private Async Sub Button_Click_Event_Handler()
    ' ... (Synchronous code)
    Await DoWorkAsync()
End Sub

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 457382

I realized to my surprise that InitDatabase() hasn't actually finished, but is still "awaiting" on SQLConnection.OpenAsync(), even though Await InitDB_t has returned!!!

This can happen if you use Async Sub. You should only use Async Sub for event handlers.

When I execute InitDatabase() I do some GUI assignments too, such as label_p.Content = "finished" which throws an exception because I am in a different thread than the GUI one.

Remove the call to Task.Run. You don't need it anyway, since your code is asynchronous.

Upvotes: 1

Related Questions