【问题标题】:WinForms UI responsiveness when dealing with "heavy" data处理“重”数据时的 WinForms UI 响应能力
【发布时间】:2010-11-09 18:02:24
【问题描述】:

我正在修改一个 Windows 窗体,以允许在 UI 保持响应时在后台加载数据。数据检索和绑定都需要相当长的时间。理想情况下,我会在后台同时进行这两项操作,但是对于我应该在后台(如在主线程之外)进行何种 UI 更新存在一些歧义。在后台显示数据检索和数据绑定的可靠示例将非常有帮助。

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      检索可以而且应该被推到后台线程 - 但有一些模式可以将其全部到位。

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

      您可以探索三种基本的后台线程方式

      • 最简单/最受限制(和古怪的 IMO)是 BackgroundWorker 组件
      • 使用 Delegates 及其 BeginInvoke()/EndInvoke() 方法可以很好地平衡易用性和灵活性(并使用 ThreadPool 线程)
      • 使用原始 Thread 对象可提供最大程度的控制,但设置起来比 ThreadPool 线程慢

      我个人倾向于委托选项;一旦你掌握了模式,它们就很容易使用。 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
      

      屏蔽 子主()

          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
      

      【讨论】:

        【解决方案3】:

        永远不要从任何后台线程更新 UI,一旦您从服务器获取数据调用回 UI 线程以更新您的 UI 绑定到的 UI 控件或数据集。

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

        HTH

        菲尔

        【讨论】:

        • 这是我最关心的问题。我很确定我不应该做 control.Datasource = myData; 之类的事情。在后台,但确实需要一些时间才能完成。所以我主要对我可以证明在后台进行的工作范围感到好奇。
        猜你喜欢
        • 1970-01-01
        • 2013-08-04
        • 2011-05-04
        • 1970-01-01
        • 2013-08-15
        • 2011-05-31
        • 2021-07-18
        • 2010-10-09
        • 1970-01-01
        相关资源
        最近更新 更多