VB.Net多个后台工作者 - 只有最后一个任务完成

时间:2011-01-26 18:39:10

标签: vb.net backgroundworker

我一直在拉着我的头发试图让它发挥作用。如果我在调试器中单步调试代码,那么一切都很好。

我的问题是,如果我只是运行它,只有最后一个任务响应。我猜我正在覆盖背景工作或其他什么。我确信我做了一些错误但是我的代码现在很麻烦,因为我在搜索时尝试了很多方法。我知道线程池和.Net 4.0任务,但很难完成我需要的工作。

基本上我正在编写一个程序(尝试更有可能),然后获取计算机和ping的列表,然后检查它们的正常运行时间并报告回来。

这在UI线程中工作正常(显然会锁定我的屏幕)。我可以让后台工作人员执行此操作,但随后它会逐个执行每台计算机,而屏幕响应时仍需要很长时间。

所以我的答案是为每个服务器启动一个新的后台工作线程有一个for循环。我的解决方案不起作用。

我已经看过其他线程,我可以做到,但我需要使用事件来调用代码,以便在每个完成后更新到UI。

最简单的方法是什么?

这是我的代码。大多数只是复制粘贴+修改,直到我正常工作。

所以在主要课程中我有测试工作者。

(我尝试使用Testworker(),但它说我不能做那个WithEvents)

当我点击按钮时,列表会加载。

Private WithEvents TestWorker As System.ComponentModel.BackgroundWorker

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
    Button1.IsEnabled = False

    Dim indexMax As Integer
                    indexMax = DataGridStatus.Items.Count
    For index = 1 To (indexMax)
        Dim Temp As ServerInfo = DataGridStatus.Items(index - 1)
        Temp.Index = index - 1
        Call_Thread(Temp)
    Next
End Sub


Private Sub Call_Thread(ByVal server As ServerInfo)
    Dim localserver As ServerInfo = server

    TestWorker = New System.ComponentModel.BackgroundWorker
    TestWorker.WorkerReportsProgress = True
    TestWorker.WorkerSupportsCancellation = True
    TestWorker.RunWorkerAsync(localserver)

End Sub

Private Sub TestWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestWorker.DoWork

    Dim iparray As IPHostEntry
    Dim ip() As IPAddress

    Dim Server As ServerInfo
    Server = e.Argument
    Try
        'Get IP Address first
        iparray = Dns.GetHostEntry(Server.ServerName)
        ip = iparray.AddressList
        Server.IPAddress = ip(0).ToString

        'Try Pinging
        Server.PingResult = PingHost(Server.ServerName)
        If Server.PingResult = "Success" Then

            'If ping success, get uptime
            Server.UpTime = GetUptime(Server.ServerName)
        Else
            Server.PingResult = "Failed"
        End If

    Catch ex As Exception
        Server.PingResult = "Error"
    End Try

    TestWorker.ReportProgress(0, Server)
    Thread.Sleep(1000)

End Sub


Private Sub TestWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles TestWorker.ProgressChanged

    Dim index As Integer
    Dim serverchange As ServerInfo = DirectCast(e.UserState, ServerInfo)

    index = DataGridStatus.Items.IndexOf(serverchange)
    ' index = serverchange.Index
    DataGridStatus.Items.Item(index) = serverchange

    ' ProgressBar1.Value = e.ProgressPercentage
    DataGridStatus.Items.Refresh()
End Sub

4 个答案:

答案 0 :(得分:3)

您只获得了最后的结果,因为每次拨打BackgroundWorker时,您的TestWorker = New System.ComponentModel.BackgroundWorker都会被吹走。由于处理是异步完成的,因此在前一个工作完成之前,在for循环中多次调用此行。

以下内容可能有效。 (对不起,我的VB生锈了;可能有更有效的表达方式。)

Delegate Function PingDelegate(ByVal server As String) As String

Private _completedCount As Int32
Private ReadOnly _lockObject As New System.Object
Dim _rnd As New Random
Private _servers As List(Of String)

Private Sub GoButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GoButton.Click
    _servers = New List(Of System.String)(New String() {"adam", "betty", "clyde", "danny", "evan", "fred", "gertrude", "hank", "ice-t", "joshua"})
    _completedCount = 0
    ListBox1.Items.Clear()
    GoButton.Enabled = False
    BackgroundWorker1.RunWorkerAsync(_servers)
End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Dim servers As List(Of System.String) = DirectCast(e.Argument, List(Of System.String))
    Dim waitHandles As New List(Of WaitHandle)

    For Each server As System.String In servers
        ' Get a delegate for the ping operation. .Net will let you call it asynchronously
        Dim d As New PingDelegate(AddressOf Ping)

        ' Start the ping operation async. When the ping is complete, it will automatically call PingIsDone
        Dim ar As IAsyncResult = d.BeginInvoke(server, AddressOf PingIsDone, d)

        ' Add the IAsyncResult for this invocation to our collection.
        waitHandles.Add(ar.AsyncWaitHandle)
    Next

    ' Wait until everything is done. This will not block the UI thread because it is happening
    ' in the background. You could also use the overload that takes a timeout value and
    ' check to see if the user has requested cancellation, for example. Once all operations
    ' are complete, this method will exit scope and the BackgroundWorker1_RunWorkerCompleted 
    ' will be called.
    WaitHandle.WaitAll(waitHandles.ToArray())
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    ListBox1.Items.Add(String.Format("{0} ({1}% done)", e.UserState, e.ProgressPercentage))
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    GoButton.Enabled = True
End Sub

Private Function Ping(ByVal server As System.String) As System.String
    ' Simulate a ping with random result and duration
    Threading.Thread.Sleep(_rnd.Next(1000, 4000))
    Dim result As Int32 = _rnd.Next(0, 2)
    If result = 0 Then
        Return server & " is ok"
    Else
        Return server & " is down"
    End If
End Function

Private Sub PingIsDone(ByVal ar As IAsyncResult)
    ' This method is called everytime a ping operation completes. Note that the order in which
    ' this method fires is completely independant of the order of the servers. The first server
    ' to respond calls this method first, etc. This keeps optimal performance.
    Dim d As PingDelegate = DirectCast(ar.AsyncState, PingDelegate)

    ' Complete the operation and get the result.
    Dim pingResult As String = d.EndInvoke(ar)

    ' To be safe, we put a lock around this so that _completedCount gets incremented atomically
    ' with the progress report. This may or may not be necessary in your application.
    SyncLock (_lockObject)
        _completedCount = _completedCount + 1
        Dim percent As Int32 = _completedCount * 100 / _servers.Count
        BackgroundWorker1.ReportProgress(percent, pingResult)
    End SyncLock
End Sub

答案 1 :(得分:1)

更新:我发布了这个答案,重点关注您从技术角度(使用许多后台工作人员)到底要做什么,而没有真正考虑这是否是一个很好的方法实现真正的目标。事实上,我认为你可以通过BackgroundWorker事件处理程序中的DoWorkParallel.ForEach循环更轻松地实现你的目标(这需要处理一个问题)很多细节工作,例如,Dave's解决方案。)


当你在VB中声明WithEvents TestWorker As BackgroundWorker时,它会将它包装起来(不完全是 - 这只是为了说明这个想法):

Private _TestWorker As BackgroundWorker
Private Property TestWorker As BackgroundWorker
    Get
        Return _TestWorker
    End Get
    Set(ByVal value As BackgroundWorker)
        ' This is all probably handled in a more thread-safe way, mind you. '

        Dim prevWorker As BackgroundWorker = _TestWorker
        If prevWorker IsNot Nothing Then
            RemoveHandler prevWorker.DoWork, AddressOf TestWorker_DoWork
            ' etc. '
        End If

        If value IsNot Nothing Then
            AddHandler value.DoWork, AddressOf TestWorker_DoWork
            ' etc. '
        End If
        _TestWorker = value
    End Set
End Property

当您意识到这一点时,很明显,每次拨打TestWorker时, BackgroundWorker设置为 Call_Thread ,您正从该字段先前引用的对象中删除任何附加的处理程序。

最明显的解决方法是在每次调用BackgroundWorker时创建一个新的本地 Call_Thread对象,在那里附加处理程序(使用AddHandlerRemoveHandler),然后让它做它的事情:

Private Sub Call_Thread(ByVal server As ServerInfo)
    Dim localserver As ServerInfo = server

    ' Use a local variable for the new worker. '
    ' This takes the place of the Private WithEvents field. '
    Dim worker As New System.ComponentModel.BackgroundWorker

    ' Set it up. '
    With worker
        .WorkerReportsProgress = True
        .WorkerSupportsCancellation = True
    End With

    ' Attach the handlers. '
    AddHandler worker.DoWork, AddressOf TestWorker_DoWork
    AddHandler worker.ProgressChanged, AdressOf TestWorker_ProgressChanged

    ' Do the work. '
    worker.RunWorkerAsync(localserver)
End Sub

只要你在UI线程中这样做,就可以在方法中创建工作者,因为BackgroundWorker会自动附加到其构造函数中的当前SynchronizationContext(如果我没记错的话)

答案 2 :(得分:0)

您需要将TestWorker_DoWorkTestWorker_ProgressChanged附加到DoWork内的ProgressChangedCall_Thread个事件。我还没有检查过剩下的代码,但这就是它现在没有做任何事情的原因。

TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
AddHandler TestWorker.DoWork, AddressOf TestWorker_DoWork
AddHandler TestWorker.ProgressChanged, AddressOf TestWorker_ProgressChanged
TestWorker.RunWorkerAsync(localserver)

答案 3 :(得分:0)

理想情况下,您应该只使用1个背景工作者并像这样使用它:

  • 汇总需要完成的所有工作:在您的情况下,列出ServerInfo
  • 在后台完成工作:ping所有服务器并保留结果
  • 报告进度:例如,在每个服务器ping通后
  • 将结果放回DoWorkEventArgs.Result
  • 在您的界面中显示结果。