views:

397

answers:

3

I'm modifying a windows form to allow data to be loaded in the background while the UI remains responsive. The data takes noticeable time to both retrieve and bind. Ideally, I would do both in the background, but there is some ambiguity about what kind of UI updates I should be doing in the background (as in outside the main thread). A solid example that shows data retrieval and data binding in the background would be very helpful.

+1  A: 

Hi,

Don't ever update the UI from any background thread, once you have fetched your data from your server invoke back to the UI thread to update the UI controls or dataset which your UI is bound to.

Using BackgroundWorker will help in this case just wire up the events.

HTH

Phil'

Philip
This was my primary concern. I was pretty sure I shouldn't be doing things like control.Datasource = myData; in the background, but it does take some time to complete. So I was mostly curious about the extent of work I could justify putting in the background.
Aethyrial
+6  A: 

The retrieval can, and should, be pushed off to a background thread--but there's some patterns to put it all in place.

Basically you'll start a background thread to retrieve the data, once it's done it will need to merge back into the UI thread to do the actual UI updates (UI updates across threads are bad bad bad).

There's three basic ways of background threading for you to explore

  • the easiest/most limited (and quirky IMO) is the BackgroundWorker component
  • using Delegates and their BeginInvoke()/EndInvoke() methods provide a nice balance of ease and flexibility (and use ThreadPool Threads)
  • using raw Thread objects provides the most control, but are slower to setup than ThreadPool Threads

Personally I lean towards the Delegates option; they're pretty easy to work with once you get the pattern down. The BackgroundWorker seems nice up front but has some gotchas and missing plumbing that make it more cumbersome to work with than you'd expect. Let me whip up a short sample of the Delegate approach; I'll update shortly...

edit

Here's some code, it's in VB but should be easy enough to transcribe if you're a C# guy. You have a couple more options on how you want the background thread to behave, so there's two samples here. Non-blocking is my prefered, but if you're fitting it into existing code then blocking might be easier for you.

Non-blocking, the callback method (GetData_Complete) will be called on the UI thread once the background thread is complete

Sub Main()

    Console.WriteLine("On the main thread")
    Dim dataDelegate As New GetDataCaller(AddressOf GetData)

    Dim iar As IAsyncResult

    ' Non-blocking approach using a callback method
    iar = dataDelegate.BeginInvoke(AddressOf GetData_Complete, Nothing)

End Sub

Private Delegate Sub GetData_CompleteCaller(ByVal iar As IAsyncResult)
Private Sub GetData_Complete(ByVal iar As IAsyncResult)
    If InvokeRequired Then
        Dim invokeDelegate As New GetData_CompleteCaller(AddressOf GetData_Complete)
        Invoke(invokeDelegate, New Object() {iar})
        Exit Sub
    End If

    ' Downcast the IAsyncResult to an AsyncResult -- it's safe and provides extra methods
    Dim ar As System.Runtime.Remoting.Messaging.AsyncResult = DirectCast(iar, System.Runtime.Remoting.Messaging.AsyncResult)

    Dim dataDelegate As GetDataCaller = DirectCast(ar.AsyncDelegate, GetDataCaller)
    Dim result As String = dataDelegate.EndInvoke(iar)

    Console.WriteLine("On the main thread again, background result is: " + result)

End Sub

Private Delegate Function GetDataCaller() As String
Private Function GetData() As String
    Console.WriteLine("On the background thread!")

    For index As Integer = 0 To 2
        Console.WriteLine("Background thread is working")
    Next

    Return "Yay, background thread got the data!"

End Function

Blocking Sub Main()

    Console.WriteLine("On the main thread")
    Dim dataDelegate As New GetDataCaller(AddressOf GetData)

    Dim iar As IAsyncResult

    ' blocking approach; WaitOne() will block this thread from proceeding until the background thread is finished
    iar = dataDelegate.BeginInvoke(Nothing, Nothing)
    iar.AsyncWaitHandle.WaitOne()
    Dim result As String = dataDelegate.EndInvoke(iar)
    Console.WriteLine("On the main thread again, background result is: " + result)

End Sub

Private Sub GetData_Complete(ByVal iar As IAsyncResult)

    ' Downcast the IAsyncResult to an AsyncResult -- it's safe and provides extra methods
    Dim ar As System.Runtime.Remoting.Messaging.AsyncResult = DirectCast(iar, System.Runtime.Remoting.Messaging.AsyncResult)

    Dim dataDelegate As GetDataCaller = DirectCast(ar.AsyncDelegate, GetDataCaller)
    Dim result As String = dataDelegate.EndInvoke(iar)

    Console.WriteLine("On the main thread again, background result is: " + result)

End Sub

Private Delegate Function GetDataCaller() As String
Private Function GetData() As String
    Console.WriteLine("On the background thread!")

    For index As Integer = 0 To 2
        Console.WriteLine("Background thread is working")
    Next

    Return "Yay, background thread got the data!"

End Function
STW
A: 

Loading (as in "retrieving from data source") might be trivial, whether you use delegates, background workers or any other protocol. But binding seems tricky, because there's not much control one can exert over it, at least in most data-bound controls - you may retrieve data asynchronously, but once you have it ready how to feed it to a large grid in the background? Is that your question? If so, I think you may either:

  • create (or subclass) your view control, providing interfaces for asynchronous load;
  • implement a paged view, showing only N records at a time, so that the UI isn't blocked while fetching/formatting records.
Humberto