How to keep GUI responsive during DB query?

All right, here's the question...while loading large amounts of data, or querying a DB, my GUI hangs...that's to be expected with single-threaded apps. So what I did is I created a borderless form with a picturebox that has the animated GIF of the progress bar scrolling back and forth, along with a label that gives you a message like "Please wait while the query executes..." I show the form before I call a method that's going to take a while to execute. Of course, the problem is that while the method is running, the app no longer refreshes the animated gif, so my little "Please wait" dialog is essentially useless. I tried to patch this by calling "Application.DoEvents" multiple times during these lengthy procedures, but that still doesn't stop it from hanging during a SQLDataAdapter.Fill execution. I've tried showing the form on different threads, and I still can't get it to work.

Obviously it can be done, since commercial applications commonly have animated wait dialogs to let you know that the app is working. Does anyone know how I can do it?

[1069 byte] By [BenCoats] at [2007-12-16]
# 1
Are you doing your DB query in a seperate thread? There is no need to call Application.DoEvents unless you're executing the query it in the same thread.

You might want to look at the documentation for System.Threading.Thread



Dim thread As New Threading.Thread(AddressOf DoQuery)
thread.Start()

MikeHull at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic General...
# 2
Yes, it's all in the same thread. I know how to actually START a new thread...but I took the other approach, and was trying to run the wait dialog on a different thread...is that not the right approach?
BenCoats at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic General...
# 3

Hi Ben,

If you're performing an asynchronous operation and need to update UI from the process or after it completes, the BackgroundWorker component (new for Whidbey) is a great option.

- In the "Components" section of the Toolbox, you'll see a component called "Background Worker". Drag it onto the form you're calling the query from.
- Jump to code for the form
- Handle the DoWork Event on the BackgroundWorker. You can put your DB query in this event handler


Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
' Call DB Query here...
End Sub

- If you want to update the UI while the Query is taking place (i.e., progress bar), handle the ProgressChanged Event. Ditto for the RunWorkerCompleted Event.

- Now all you need to do is tell the BackgroundWorker when to start doing its work:


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
With Me.BackgroundWorker1
.WorkerReportsProgress =
True
.WorkerSupportsCancellation = True
.RunWorkerAsync()
End With
End Sub

I hope this helps!

Joe
The VB Team

Joe_MS at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic General...
# 4
Joe,
Thanks for your reply. The only reason that I haven't used the BackgroundWorker component thus far is because I am not sure how to go about passing all the parameters I am using for the query to the BackgroundWorker. I have a search form, where you can choose the fields/operators/field relationships, etc. When you start the search, certain functions process all that input and build a SQL string...then data adaptor, fill table, build report, view report, etc. I know that I can't reference objects created in one thread from another thread. I could build a class with fields for each of these parameters IF I knew how to pass it to the background worker; but I do not. Hence, I have (until now) focused on running the query on the main thread and trying to support a wait dialog on the other thread.

Is there a method for passing arguments to the BackgroundWorker component? How would you suggest doing this?

BenCoats at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic General...
# 5

Hi Ben,

I would still stay the path using BackgroundWorkerProcess rather than coming up with your own ThreadingScheme. You will save a lot of time and pain.

There is a way to pass arguments in a threadsafe way using BWP. You just need to set an argument when calling BWP.RunWorkerAsync(argument). You can fetch that data at anytime using the e.Argument field passed into the event handlers. You can also pass data results using the same trick via the e.Result property.

Here is a sample pulling it together:



Private Sub btnRunQuery_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRunQuery.Click

Me.BackgroundWorker1.RunWorkerAsync(Me.txtFilterString.Text)

End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

Dim filterString As String = TryCast(e.Argument, String)

If filterString IsNot Nothing Then

'call database and retrive data object, e.g.

e.Result = dataComponent.getData(filterString)

'note returned data is stored in e.Result

End If

End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

Dim returnedData As DataTable = TryCast(e.Result, DataTable)

Me.bindingSource.DataSource = returnedData

End Sub



Hope this helps. Let us know.

Paul Yuknewicz
Visual Basic

PaulYuk_MS at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic General...