处理“繁重”数据时,WinForms UI响应能力

时间:2009-07-10 15:02:24

标签: winforms user-interface data-binding multithreading backgroundworker

我正在修改Windows窗体,以便在UI保持响应时允许在后台加载数据。数据需要花费大量时间来检索和绑定。理想情况下,我会在后台执行这两项工作,但是我在后台应该做什么样的UI更新(如在主线程外部),这有些含糊不清。在后台显示数据检索和数据绑定的一个可靠示例将非常有用。

3 个答案:

答案 0 :(得分:6)

检索可以而且应该被推送到后台线程 - 但是有一些模式可以将它们全部放在适当位置。

基本上你会启动一个后台线程来检索数据,一旦完成它就需要合并回UI线程来进行实际的UI更新(跨线程的UI更新是糟糕的坏事)。

有三种基本的背景线程方法供您探索

  • 最简单/最有限(和古怪的IMO)是BackgroundWorker组件
  • 使用Delegates及其BeginInvoke()/ EndInvoke()方法提供了轻松和灵活性的平衡(并使用ThreadPool线程)
  • 使用原始Thread对象提供最多的控件,但设置比ThreadPool Threads

我个人倾向于代表选项;一旦你获得模式,它们就很容易合作。 BackgroundWorker在前面看起来不错,但有一些问题和缺少的管道,使得它比你期望的更麻烦。让我简单介绍一下代表方法的样本;我很快就会更新......

修改

这是一些代码,它在VB中,但如果你是C#家伙,应该很容易转录。关于你希望后台线程的行为方式你还有几个选项,所以这里有两个示例。非阻塞是我的首选,但如果您将其融入现有代码中,那么阻止对您来说可能更容易。

非阻塞,一旦后台线程完成,将在UI线程上调用回调方法(GetData_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

<强>禁止     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

答案 1 :(得分:1)

从服务器获取数据后,不要从任何后台线程更新UI,调用UI线程来更新UI绑定的UI控件或数据集。

在这种情况下,使用BackgroundWorker将有助于连接事件。

HTH

菲尔'

答案 2 :(得分:0)

加载(如“从数据源检索”)可能是微不足道的,无论您是使用委托,后台工作者还是任何其他协议。但是绑定似乎很棘手,因为没有太多的控制可以施加它,至少在大多数数据绑定控件中 - 你可以异步地检索数据,但是一旦你准备好它如何将它提供给后台的大网格?那是你的问题吗?如果是这样,我想你可能会:

  • 创建(或子类)视图控件,为异步加载提供接口;
  • 实现分页视图,一次只显示N条记录,以便在获取/格式化记录时不会阻止UI。