【问题标题】:WPF Background Worker and ProgressBarWPF 后台工作者和 ProgressBar
【发布时间】:2012-12-22 12:47:58
【问题描述】:

所以,我有一个 wpf 表单,它发送到一个站点,解析 html,并返回一个强类型的 'href' 值列表。 (是的,这是我自己的网站)

我正在使用一个后台工作者来释放 UI 的挂起,并呈现一个正在运行的进度条。

虽然它只适用于网站的第一页,但如果我决定递归网站,进度条会挂起,而递归正在发生,然后一旦递归完成,进度条就会恢复活力。

你能告诉我我在这里做错了什么吗?并且可能会指导我正确使用带有进度条的后台工作人员...基本上,进度条应该在执行任务时运行,但我根据代码假设情况并非如此。

这是正在执行此操作的窗口的代码隐藏:

Imports System.Threading.Tasks
Imports System.Threading

Class MainWindow

Private _previousCursor As Cursor = Mouse.OverrideCursor
Private _Spider As New Spider.SpiderIt
Private _Worker As New ComponentModel.BackgroundWorker
Private _RunCount As Integer = 0

Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    Me.workProgress.Visibility = Windows.Visibility.Hidden
    _Worker.WorkerReportsProgress = True
    _Worker.WorkerSupportsCancellation = True
    AddHandler _Worker.DoWork, New System.ComponentModel.DoWorkEventHandler(AddressOf Spider)
    AddHandler _Worker.ProgressChanged, New System.ComponentModel.ProgressChangedEventHandler(AddressOf worker_ProgressChanged)
    AddHandler _Worker.RunWorkerCompleted, New System.ComponentModel.RunWorkerCompletedEventHandler(AddressOf worker_RunWorkerCompleted)
    Me.SiteParse.Focus()
End Sub

Private Sub SiteParseKeyDown(sender As System.Object, e As System.Windows.Input.KeyEventArgs)
    If (e.Key = Key.Return) Then
        Me.btnParseAll.IsEnabled = False
        Me.btnParseSelected.IsEnabled = False
        Me.SiteParse.IsEnabled = False
        Mouse.OverrideCursor = Cursors.Wait
        Me.workProgress.Visibility = Windows.Visibility.Visible
        _Worker.RunWorkerAsync(New Typing() With {.Url = SiteParse.Text, .Recurse = Recurse.IsChecked})
    End If
End Sub

Private Sub btnParseAll_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnParseAll.Click
    Me.btnParseAll.IsEnabled = False
    Me.btnParseSelected.IsEnabled = False
    Me.SiteParse.IsEnabled = False
    Dim _TL As New List(Of DGTyping)
    Using New WaitCursor
        For Each Item In DG_SiteLinks.Items
            _TL.Add(New DGTyping() With {
                    .SiteUrl = Item.SiteUrl,
                    .SiteTitle = Item.SiteTitle
                })
        Next
    End Using
    Dim _T As New ParseLinks(Me, _TL)
    _T.ShowDialog()
End Sub

Private Sub btnParseSelected_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnParseSelected.Click
    Me.btnParseAll.IsEnabled = False
    Me.btnParseSelected.IsEnabled = False
    Me.SiteParse.IsEnabled = False
    Dim _TL As New List(Of DGTyping)
    Using New WaitCursor
        For Each Item In DG_SiteLinks.SelectedItems
            _TL.Add(New DGTyping() With {
                    .SiteUrl = Item.SiteUrl,
                    .SiteTitle = Item.SiteTitle
                })
        Next
    End Using
    Dim _T As New ParseLinks(Me, _TL)
    _T.ShowDialog()
End Sub

#Region "Get Site Links"

Private Sub Spider(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
    'Do the work here, but need to get the value of SiteParse first
    With _Spider
        .UrlToParse = DirectCast(e.Argument.Url, String)
        .ShouldRecurse = DirectCast(e.Argument.Recurse, Boolean)
        .RecurseLevels = 20
        .SpiderIt(_Worker)
    End With
End Sub

Private Sub worker_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs)
    workProgress.Value = e.ProgressPercentage
End Sub

Private Sub worker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs)
    EndRest()
    Dim _IL As List(Of Spider.Typing.InternalLinks)
    _IL = _Spider.InternalLinks()
    Dim _TL As New List(Of DGTyping)
    For Each item In _IL
        _TL.Add(New DGTyping() With {
                .SiteUrl = item.Url,
                .SiteTitle = If(item.Title.Length > 0, item.Title, item.Content)
            })
    Next
    _IL.Clear()
    Me.DG_SiteLinks.ItemsSource = _TL
    EndSync()
End Sub

Private Sub BrowseSite(sender As Object, e As RoutedEventArgs)
    Dim _URL As String = DirectCast(sender, TextBlock).Text
    Dim _T As New Browser(_URL)
    _T.ShowDialog()
End Sub

Private Sub Window_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs)
    If _Worker IsNot Nothing Then
        If _Worker.IsBusy Then
            _Worker.CancelAsync()
        End If
    End If
End Sub

Private Sub EndSync()
    _Worker.CancelAsync()
    _Worker.Dispose()
    _Spider.Dispose()
End Sub

Private Sub EndRest()
    workProgress.Value = 0
    workProgress.Visibility = Windows.Visibility.Hidden
    Me.btnParseAll.IsEnabled = True
    Me.btnParseSelected.IsEnabled = True
    Me.SiteParse.IsEnabled = True
    Mouse.OverrideCursor = _previousCursor
End Sub

Partial Public Class Typing
    Public Property Url As String
    Public Property Recurse As Boolean
End Class

Partial Public Class DGTyping
    Public Property SiteUrl As String
    Public Property SiteTitle As String
End Class

#End Region

End Class

.SpiderIt() 输出指定的站点,将 html 作为 HDocument (SuperstarCoders LinqToHtml) 抓取,解析它以查找内部链接,并将它们放入强类型列表中。这是在单独的类程序集中完成的,并且性能完美。

SpiderIt 方法和包含类:

Imports Superstar.Html.Linq
Imports System.Threading.Tasks

Public Class SpiderIt
Implements IDisposable

#Region "Public Properties"

''' <summary>
''' Specify the initial URL to parse
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property UrlToParse As String

''' <summary>
''' Should this recurse the internal links of the site
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ShouldRecurse As Boolean = False

''' <summary>
''' Specify the number of levels to recurse
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property RecurseLevels As Long = 0

''' <summary>
''' Returns a message from the SpiderIt method
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property Message() As String
    Get
        Return _Msg
    End Get
End Property

''' <summary>
''' Returns a strongly typed list of internal links
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property InternalLinks() As List(Of Typing.InternalLinks)
    Get
        Return _InternalLinkList
    End Get
End Property

''' <summary>
''' Returns a strongly typed list of external links
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ExternalLinks() As List(Of Typing.ExternalLinks)
    Get
        Return _ExternalLinkList
    End Get
End Property

#End Region

#Region "Internal Properties"

Private disposedValue As Boolean
Private _Msg As String
Private _Ctr As Long = 0
Private _InternalLinkList As New List(Of Typing.InternalLinks)
Private _ExternalLinkList As New List(Of Typing.ExternalLinks)
Private _DLer As New Downloader
Private _RCt As Long = 0

#End Region

#Region "Public Methods"

''' <summary>
''' Parse with the specified values
''' </summary>
''' <returns>Boolean</returns>
''' <remarks>Returns true or false, based on if it has completed, as well as a message
''' Spits out 2 strongly typed lists.  Both internal and external URLs
''' </remarks>
Public Function SpiderIt(ByVal _Worker) As Boolean
    For i As Integer = 1 To 99
        _Worker.ReportProgress(i)
        System.Threading.Thread.Sleep(50)
    Next
    _Worker.ReportProgress(100)
    Dim _Doc As HDocument = _DLer.DownloadHDoc(UrlToParse)
    With _Doc
        If _Doc Is Nothing Then
            _Msg = "There is no document to parse."
            Return False
        Else
            Try
                Dim _AL = .Descendants("a")
                'Parse the internal links
                ParseLinks(_AL)
                _Msg = "Internal Link List Built"
                Return True
            Catch ex As Exception
                _Msg = ex.Message
                Return False
            End Try
        End If
    End With
End Function

#End Region

#Region "Internal Methods"

#Region "Spider Helpers"

Private Sub ParseLinks(ByVal _AL As IEnumerable(Of HElement))
    Try
        Dim _Link As String, _D As HDocument
        For i As Long = 0 To _AL.Count - 1
            If _AL(i).Attribute("href") IsNot Nothing AndAlso Not (_AL(i).Attribute("href").Value.Contains("//") OrElse
                    _AL(i).Attribute("href").Value.Contains("http://") OrElse
                    _AL(i).Attribute("href").Value.Contains("https://") OrElse
                    _AL(i).Attribute("href").Value.Contains("ftp://") OrElse
                    _AL(i).Attribute("href").Value.Contains("mailto:") OrElse
                    _AL(i).Attribute("href").Value.Contains("#")) Then
                _Link = UrlToParse & "/" & _AL(i).Attribute("href").Value
                If Not (_InternalLinkList.Any(Function(x) x.Url = _Link.Replace("//", "/").Replace("http:/", "http://").Replace("https:/", "https://"))) Then
                    AddInternalLinks(_Link.Replace("//", "/").Replace("http:/", "http://").Replace("https:/", "https://"),
                                     If(_AL(i).Attribute("target") Is Nothing,
                                         String.Empty,
                                         _AL(i).Attribute("target").Value),
                                     _AL(i).Value,
                                     If(_AL(i).Attribute("title") Is Nothing,
                                         String.Empty,
                                         _AL(i).Attribute("title").Value))
                    If ShouldRecurse Then
                        _RCt += 1
                        If _RCt <= RecurseLevels Then
                            _D = _DLer.DownloadHDoc(_Link)
                            ParseLinks(_D.Descendants("a"))
                        End If
                    End If
                End If
            Else
                _Link = _AL(i).Attribute("href").Value
                If Not (_ExternalLinkList.Any(Function(x) x.Url = _Link)) Then
                    AddExternalLinks(_Link,
                                     If(_AL(i).Attribute("target") Is Nothing,
                                         String.Empty,
                                         _AL(i).Attribute("target").Value),
                                     _AL(i).Value,
                                     If(_AL(i).Attribute("title") Is Nothing,
                                         String.Empty,
                                         _AL(i).Attribute("title").Value))
                End If
            End If
        Next
    Catch ex As Exception
        _Msg += ex.StackTrace
    End Try
End Sub

Private Sub AddExternalLinks(ByVal _Link As String, ByVal _Target As String, ByVal _Content As String, ByVal _Title As String)
    Try
        _ExternalLinkList.Add(New Typing.ExternalLinks With {
                            .Url = _Link,
                            .Content = _Content,
                            .Target = _Target,
                            .Title = _Title
                        })
    Catch ex As Exception
        _Msg += ex.StackTrace
    End Try
End Sub

Private Sub AddInternalLinks(ByVal _Link As String, ByVal _Target As String, ByVal _Content As String, ByVal _Title As String)
    Try
        _InternalLinkList.Add(New Typing.InternalLinks With {
                            .Url = _Link,
                            .Content = _Content,
                            .Target = _Target,
                            .Title = _Title
                        })
    Catch ex As Exception
        _Msg += ex.StackTrace
    End Try
End Sub

#End Region

#Region "IDisposable Support"

Protected Overridable Sub Dispose(disposing As Boolean)
    If Not Me.disposedValue Then
        If disposing Then
        End If
        _Msg = String.Empty
        _InternalLinkList.Clear()
        _ExternalLinkList.Clear()
        _DLer.Dispose()
    End If
    Me.disposedValue = True
End Sub

Public Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(Me)
End Sub

#End Region

#End Region

End Class

【问题讨论】:

  • 我不确定,所以我不会将此作为答案发布,但似乎您的函数 SpiderIt() 是以模态方式调用的。退出这个函数你开始一个循环来推进进度条,但此时你已经完成了解析。如果您递归,也会发生同样的事情,但当然需要更多时间来完成解析,并且您会注意到进度条中的延迟。我认为您应该在 SpiderIt 函数内调用 ReportProgress 来计算该函数内完成的工作百分比。 (看看相关代码就好了)
  • 所以我假设我应该将 _Worker 传递给 SpiderIt(),但是,这个类如何获得回报?我会试验
  • 看实际代码,不需要SpiderIt()的任何返回,调用SpiderIt()时传入_worker即可,不过,当然我没有完全理解你的代码。
  • 好的。它仍然可以这样做,但是,根据正在完成的实际工作,它仍然没有进展。我将编辑问题以放入 SpiderIt 所在的班级。请同时查看原件,因为它略有变化
  • 你有一个类和一个名为 SpiderIt 的函数

标签: .net wpf backgroundworker


【解决方案1】:

我将其发布为答案,因为作为评论文本太长了。

也许我遗漏了一些东西(阅读这种格式的代码很痛苦)。

您从 1 数到 99,并每 50 毫秒报告一次进度。在两者之间似乎什么都没有发生,我的意思是工作量会增加一些真正的延迟。然后你报告 100%,然后它似乎实际上是在加载文档和解析,我猜这需要一段时间。

您不应该在 ParseLinks() 方法中的某处抛出 ReportProgress()。当然,您必须能够计算要解析的节点数,以便您可以在工作完成时以与 100% 进度一致的速度报告进度。

编写另一个递归方法,它只提前计算节点数(应该很快),然后用这个数字武装你将知道传递给 ReportProgress() 的值(你应该再次在 ParseLinks() 中调用) 所以你会有一个高达 100% 的稳定进度。 (显然,您必须将对 BackgroundWorker 的引用传递给 ParseLinks() )

这可能很困难,但没有人说它会很容易:D。

干杯。

【讨论】:

  • 是的。以为那可能是要走的路……(一旦填充了该列表,我就在其他地方做这件事)……我必须过期一点,但是如果我没记错的话…….decendants(“ a") 是一个可枚举的接口……应该有一个计数方法,我可以从中获取数字……可能有某种哦。在哪里只能获取内部链接……
  • hmmm....无论如何,我最终将不得不在另一个递归函数中执行此操作....这样我就可以获得完整的 url 计数...谢谢...最终会这个假期过后。 :)
  • :) 你也是。所以,我撒了谎……我今天趁它还新鲜的时候跳了上去。你们是我发誓的天赐之物!大声笑无论如何...只要我不必递归就可以很好地工作,一旦启用递归它就会失败,所以我认为我将不得不做一个单独的递归函数来获得完整的计数。这样做的问题是,如果它是一个有大量内部链接的网站,它会大大减慢它
  • 嗯哈哈。所以,这里有一些很时髦的东西,但我想我会保持原样。它正在工作,并且非常适合非递归。当我将它实现到递归中时,prgress bar 重新开始......事实上,由于递归,它重新开始多次......但我有点喜欢这样,因为它表明工作正在完成,结束由于递归而结束。你能想到保持这样的任何警告吗,因为我真的想保持这样做=)
  • 不,没关系。这显然是您和我在许多其他产品(尤其是软件安装)中看到的一种行为。这是一种妥协,有时您必须这样做,只要它符合您的新要求,它就是完美的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
相关资源
最近更新 更多