【问题标题】:Backgroundworker - UI still not responsive until BGW completedBackgroundworker - 在 BGW 完成之前,UI 仍然没有响应
【发布时间】:2013-08-01 15:14:08
【问题描述】:

我为我的应用程序实现了一个 BGW,希望加快启动窗口的加载时间(40 多个控件)

我不会发布我的整个代码,因为它太长了,但会给你代码的要点。我将需要时间完成的大函数调用与少数控件一起拆分,并将它们移到 BGW 中,希望异步加载控件以帮助加快进程。

据了解,我必须将 UI 更改代码移动到 ProgressChanged 事件或 RunWorkerCompleted 事件,我已经这样做了。我最初将所有代码都扔到了 DoWork 事件中,它非常快,但发现它不安全,所以我重新设计了它以将所有与 UI 相关的 oode 移动到 ProgressChanged。现在几乎没有那么快了,而且 BGW 控件似乎要等到 UI 线程完成后再更改 BGW_ProgressCHanged 事件中的控件。当我对 DoWork 进行所有更改时,我从未见过两者之间的这种“滞后”。我能做些什么呢?或者我至少可以让 BGW 实时更新控件,而不是等待所有控件完成后再更新所有控件?

响应速度较低,并且会锁定窗口以等待 BGW 控件更新。只是寻找可能发生的事情

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SyncLock <globalVar>
                    BackgroundWorker1.ReportProgress(0, "Tom")

End SyncLock
End Sub

 Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
  Dim value As String = DirectCast(e.UserState, String)
 Select Case e.ProgressPercentage

Case 0 
 lblName.text = value
 lblName.Visible = true
End Select
End Sub

【问题讨论】:

  • 您在一个对象上有一个SyncLock。如果 UI 线程还试图在 BackgroundWorker 运行时锁定该对象,则会被阻止。
  • 我的印象是 SyncLock 将是在另一个线程中安全地使用共享全局变量的唯一方法。有没有更好的办法?
  • 根本不允许BackgroundWorker 访问该变量。返回结果并更新RunWorkerCompleted 事件处理程序中的变量。
  • 我正在努力理解。我拥有 BGW_DoWork 事件中所有控件的所有逻辑,为了使用该逻辑,我需要使用该变量,该变量只能在整个项目中声明一次。
  • BackgroundWorker 可以对变量的副本进行操作。您也可以尝试将锁定的范围最小化到实际访问它的语句,而不是为整个 DoWork 事件处理程序锁定它。

标签: vb.net multithreading winforms backgroundworker


【解决方案1】:

您删除了代码中存在问题的所有证据,但诊断结果非常匹配。您遇到了消防水带问题。您的代码过于频繁地调用 ReportProgress。

当您的 ProgressChanged 事件处理程序需要更多时间而不是 ReportProgress 调用之间的时间时,事情就会出错。这就像从消防水带里喝水一样,无论你吞下水的速度有多快,都跟不上水流。

这是 UI 线程所经历的。当它完成对 ProgressChanged 事件处理程序的调用时,还有更多的水,还有另一个调用处理程序的请求。在 UI 线程无法跟上的情况下,这种情况会无情地继续下去。它现在不再履行其正常职责。这意味着您的 UI 停止更新,不再执行绘制。它不再响应输入。

这可能会持续一段时间工作线程停止运行后,UI 仍在尝试处理积压的请求。当它最终到达那里时,用户界面突然恢复生机。

诊断这种情况的一种简单方法是在 ReportProgress 调用之后添加此方法调用:

   System.Threading.Thread.Sleep(45)

这会减慢工作线程的速度,从而将 ReportProgress() 调用的次数限制为每秒不超过 21 次。这对于人眼来说已经足够快了,任何更快的东西看起来都像模糊,所以是浪费精力。如果这样可以解决问题,那么您就找到了原因。

像这样使用 Sleep() 是一个丑陋的 Q&D 解决方案,它当然也会减慢你的工作人员的速度,因此它完成的工作更少。您必须改进您的代码,以免发生这种情况,并减少 ReportProgress 调用。

【讨论】:

  • 感谢您的信息!我会尝试这个,有没有更好,更有效的方法来与后台工作人员一起做这件事?我觉得为每个组件多次调用 ProgressChanged 并不是最好的方法,也不打算以这种方式。
  • 如果您首先尝试这些建议,然后考虑您将如何处理您的发现,这总是会更好。如果我对您的代码一无所知,“有没有更好的方法”是无法回答的。
  • 我确实尝试了您的建议,但没有看到速度的变化。所以我怀疑这是一些潜在的问题。我会继续搜索,再次感谢
【解决方案2】:

在启动 Worker 之前您可能需要添加一件事:

    Me.SuspendLayout()

然后,在RunWorkerCompleted 事件中:

    Me.ResumeLayout()

这应该暂停所有布局逻辑,直到所有工作完成,然后在 1 次操作中更新整个表单。

编辑

替换

BackgroundWorker1.ReportProgress(...)

DirectCast(sender, BackgroundWorker).ReportProgress(...)

并摆脱同步锁。

EDIT2

将数据提供给DoWork 事件的正确方法是通过DoWorkEventArgs

像这样开始工作:

BackgroundWorker1.RunWorkerAsync(<globalvar>)

然后像这样访问它:

Dim globalVarData As Object = e.Argument

【讨论】:

  • 这就是它现在正在做的事情。有没有办法让它实时更新控件,而不是在更新 UI 之前等待 BGW 中的所有工作完成?
  • 虽然这可能会给您一种运行速度更快的印象,但实际上需要更长的时间。顺便说一句,我在您的代码 sn-p 中看不到任何阻止布局更新的内容,直到 BGW 完成其工作。
  • 有趣。你能详细说明一下吗?至于代码,我没有任何东西阻止它更新。主 UI 线程通过其控件和逻辑,在 BGW 的代码异步工作时更新 UI,但是在 BGW 完成之前,UI 不会更新(BGW 控件)
  • (重新)在屏幕上绘制控件需要时间,并且每次更改表单上的某些内容(在本例中为标签)时都会发生。如果您有很多变化的控件,windows 将不得不多次执行这些绘图操作。在所有控件都处于最终状态之前暂停布局逻辑可以消除这种开销。
  • @Criel 我添加了另一个可以改进您的代码的建议。
猜你喜欢
  • 1970-01-01
  • 2013-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-16
  • 2017-09-30
相关资源
最近更新 更多