Reputation:
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
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
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
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