WinForms UI 在处理“繁重”任务时的响应能力 数据

发布于 2024-07-26 20:40:53 字数 156 浏览 7 评论 0原文

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

怕倦 2024-08-02 20:40:53

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

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

后台线程有三种基本方法供您探索,

  • 最简单/最有限(也是古怪的 IMO)是使用 Delegates 的 BackgroundWorker 组件
  • ,它们的 BeginInvoke()/EndInvoke() 方法提供了易用性和灵活性的良好平衡(并使用 ThreadPool 线程) )
  • 使用原始 Thread 对象提供了最大程度的控制,但设置速度比 ThreadPool Threads 慢。

我个人倾向于 Delegates 选项; 一旦你掌握了模式,它们就很容易使用。 从前面看,BackgroundWorker 看起来不错,但有一些问题和缺失的管道,使其使用起来比您想象的更加麻烦。 让我快速制作一个委托方法的简短示例; 我很快就会更新...

编辑

这里有一些代码,它是用 VB 编写的,但如果您是 C# 人员,应该很容易转录。 关于后台线程的行为方式,您还有更多选择,因此这里有两个示例。 非阻塞是我的首选,但如果您将其安装到现有代码中,那么阻塞可能对您来说更容易。

非阻塞,一旦后台线程完成,回调方法(GetData_Complete)将在UI线程上调用

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

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
猫弦 2024-08-02 20:40:53

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

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

HTH

菲尔'

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'

天冷不及心凉 2024-08-02 20:40:53

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

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

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文