【问题标题】:VB.Net Multiple background workers - Only last task completesVB.Net 多个后台工作人员 - 只有最后一个任务完成
【发布时间】:2011-01-27 04:56:21
【问题描述】:

我一直在努力解决这个问题。如果我在调试器中逐步执行代码,一切都会很好。

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

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

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

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

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

最简单的方法是什么?

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

所以在主课中我有 testworker。

(我尝试使用 Testworker() 但它说我无法使用事件)

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

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

【问题讨论】:

  • 你考虑过使用 Ping.SendAsync() 吗?

标签: vb.net backgroundworker


【解决方案1】:

您只会得到最后一个结果,因为您每次调用 TestWorker = New System.ComponentModel.BackgroundWorker 时都会把您的 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

【讨论】:

  • 戴夫,我想是这样的。我看到您可以将后台工作人员声明为 Testworker() 但它不允许我使用我需要的“WithEvents”来做到这一点。将等待您的回复。希望你能告诉我正确的方法。
  • @Jasin:您不需要需要WithEvents 来处理您的工作人员的事件。在 VB 中,动态执行此操作的方法是使用AddHandlerRemoveHandler。例如,请参阅我的答案。
  • 哇。好吧,起初我以为你只是在使用 1 个后台工作人员。但是您正在使用后台工作人员然后调用线程来进行 ping。我会花一点时间来理解它,但这很有帮助并且效果很好。只需要将它包含在我的代码中。非常感谢
  • 对。从某种意义上说,这使得BackgroundWorker 无关紧要,但它确实提供了自动知道如何将事情报告回 UI 线程的便利,因此您不必在那里手动调用它们。但繁重的工作是使用异步编程模型完成的。
  • 刚刚看到您添加了 cmets。再次感谢,回答了我的其他问题。
【解决方案2】:

更新:我发布了这个答案,重点关注您从技术角度(使用许多后台工作人员)尝试做的事情,而没有真正考虑这是否是一个好方法完成你的真正的目标。事实上,我认为你可以通过一个BackgroundWorkerParallel.ForEach 事件处理程序中的Parallel.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

当您意识到这一点时,很明显,通过在每次调用 Call_Thread 时将TestWorker 设置 BackgroundWorker,您正在删除任何附加的来自该字段先前引用的对象的处理程序。

最明显的解决方法是在每次调用Call_Thread 时创建一个新的本地 BackgroundWorker 对象,将处理程序附加到那里(使用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(如果我没记错的话)。

【讨论】:

  • Dan,谢谢,我正在查看 AddHandler 事件,但找不到传递和接收值的方法。
  • @Jasin: AddHandler 就是这样做的:添加处理程序。您仍然会调用RunWorkerAsync(就像您在问题中的代码中一样)来引发事件并以相同的方式传递值。
  • Dan 所以你的意思是当我 RunWorkerAsync 时(我可以在这里包含函数通常期望的值{在我的情况下是自定义类})谢谢你我一开始不明白。
  • @Jasin:查看我的编辑。希望这有助于说明我的意思?
  • 是的,先生,非常感谢。我会给你积分,但没有代表可以做任何事情。
【解决方案3】:

理想情况下,您应该只使用 1 个后台工作人员并像这样使用它:

  • 组装所有需要完成的工作:在您的情况下是 ServerInfo 列表
  • 在后台执行工作:ping 所有服务器并保留结果
  • 报告进度:例如在每个服务器 ping 之后
  • 将结果放回 DoWorkEventArgs.Result
  • 在您的 UI 中显示结果。

【讨论】:

  • Zippy,谢谢,我可能不得不求助于它。我想使用多个线程,因为我们计划一次 ping 并从 100 台左右的服务器获取信息,因此能够同时 ping 多个以加快进程会很好。
【解决方案4】:

您需要将TestWorker_DoWorkTestWorker_ProgressChanged 附加到Call_Thread 中的DoWorkProgressChanged 事件。我还没有检查其余的代码,但这就是它现在没有做任何事情的原因。

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)

【讨论】:

  • Adam,谢谢我试过了,但是我得到了两个错误,说这两个是事件,不能直接引发。列表中的最后一台计算机确实显示正确。
  • Adam,在 VB 中添加/删除处理程序的语法是 AddHandlerRemoveHandler 而不是 += 运算符 C# 使用(我认为这就是为什么 @Jasin 被你的代码)。
  • @Jasin:很抱歉;虽然我复制了您的 VB.NET 代码,但出于某种原因,我用 C# 编写了事件附加代码。这现在应该对你有用。 @Dan:呃,谢谢!不知道我在想什么!
猜你喜欢
  • 2022-07-06
  • 1970-01-01
  • 1970-01-01
  • 2014-04-27
  • 1970-01-01
  • 2020-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多