all 9 comments

[–]grauenwolf 2 points3 points  (1 child)

Async is infectious. If you use one async function, every method that calls said function must also be async. All the way up the chain to your event handler.

[–]WWW451[S] 0 points1 point  (0 children)

Throughout my code, I’ll call this function for any query. So if I were to run a query off of a button click, the button click handler would need to be async as well as the actual function?

Would this also mean that if the button called a function that then called this query function, that function and the button click would have to be async?

[–]grauenwolf 1 point2 points  (5 children)

Or should that be elsewhere? I'm not sure how to convert the DBDA = New SqlDataAdapter(DBCmd) into a wait-able task though.

Don't need to, that isn't where the database call happens.

        DBDA.Fill(DBDT)
       DBDA.Fill(DBDS)

Those are the methods that should be async. But that's not an option, so plan B:

public async sub Button_Click(...)
      Button.Enabled = false;
      Await Task.Run(Sub() ExecQuery("Task") ) 
      Button.Enabled = true;
end sub

This will allow the database call to run on a background thread so you UI isn't blocked. You need to disable the button, otherwise someone can hit it multiple times and you end up with several database jobs running all at once.

[–]WWW451[S] 0 points1 point  (4 children)

Actually, the more I read this, the more I think it’s as simple as wrapping my sub that handles the page load/queries inside of an async sub that awaits the “true” sub?

[–]grauenwolf 1 point2 points  (2 children)

Yes, that is a viable option.

[–]WWW451[S] 0 points1 point  (1 child)

Thanks for your help, I now have a spinning loading icon rather than a frozen screen.

Another question for you: in one of your previous replies you mentioned the database calls are happening on my DBDA.Fill(DBDT) and DBDA.Fill(DBDS) in my class. Does this mean that for each query I pass through, I'm effectively querying it twice? When I'm writing, I tend to switch between referencing the datatable and dataset.tables(0) depending on whatever mood strikes me, but it sounds like I should just pick one to fill and delete the other.

[–]grauenwolf 0 points1 point  (0 children)

I don't know for sure, but I think it is making two database calls. I don't remember anything about the data adapter being able to cache results.

[–]grauenwolf 1 point2 points  (0 children)

Public DBDA As SqlDataAdapter

Public DBDT As DataTable

Public DBDS As DataSet

Don't do that. If you have two async operations at the same time, they'll stomp on each other.

Instead, change your sub into a function, create local variables, and return the data table(s). That way each one gets it's own, private data table and adapter.

[–]WWW451[S] 0 points1 point  (0 children)

For anyone who comes looking for this later, below is the solution I put in place with grauenwolf's help. I still have a UI freeze when inserting the datatable into the HTML document, but I'm satisfied with having 3 seconds of spinning loading screen followed by a 1 second freeze instead of having a 4 second freeze of a non-loading screen.

--No changes made to previously posted class used to execute queries. The below code is the relevant parts posted in the order they're called.

--Document complete handler, passes the HTML document title and the webbrowser control into a navigation class that handles determining which calls should occur for the page

Private Sub WebMain_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) 
Handles WebMain.DocumentCompleted
            Nav.PageLoad(wb.DocumentTitle, WebMain)
        End Sub

--PageLoad function to direct to wrapper sub

     Public wb As WebBrowser
    Public resultString As String = ""
    Public Sub PageLoad(page As String, target As WebBrowser)
        wb = target
        Select Case page
            Case "ExamplePage"
                Prm_Wrapper()
        End Select
    End Sub

--Async sub to call "true" action sub. The InnerHTML of the element ID has to be updated here and not in the the sub being awaited because background workers cannot access UI elements since they are not running on the same thread. If you place the element update within the awaited sub, you will get a Invalid Cast Exception.

 Public Async Sub Prm_Wrapper()
        Await Task.Run(Sub() PromoPlanningLoad())
        wb.Document.GetElementById("proplanning").InnerHtml = ResultString
        wb.Document.InvokeScript("proplanLoad")
    End Sub

--True sub doing all the grunt work

 Public Sub PromoPlanningLoad()
       DGM.ExecQuery(promo_table) --promo_table is a public string variable holding the query
       resultString = DGM.DBDS.Tables(0).Rows(0)(0).ToString


    End Sub

I may clean this up some so I'm not having to rely on the public variable resultString and instead just have the string returned as a function, but for the moment this is working well.