VB.Net Несколько фоновых работников - только последняя задача завершена - PullRequest
0 голосов
/ 26 января 2011

Я вырывал свои волосы, пытаясь заставить это работать.Если я перешагиваю код в отладчике, все работает отлично.

Моя проблема в том, что если я просто запустил его, откликнется только последнее задание.Я предполагаю, что я перезаписываю фоновый рабочий или что-то.Я уверен, что я делаю несколько вещей неправильно, но мой код теперь грязный, так как я много пробовал во время поиска.Я знаю о задачах threadpool и .Net 4.0, но с трудом справляюсь с тем, что мне нужно.

В основном я пишу программу (более вероятно), которая затем берет список компьютеров и пингов, затемпроверяет время их работы и сообщает обратно.

Это нормально работает в потоке пользовательского интерфейса (очевидно, это блокирует мой экран).Я могу сделать так, чтобы фоновый работник просто делал это, но затем он выполнял каждый компьютер по одному, и хотя экран реагировал, он все еще занимал много времени.

Поэтому мой ответ заключался в том, чтобы иметь цикл for для каждогосервер запускает новый фоновый рабочий поток.Мое решение не работает.

Я видел другие потоки, которые я мог бы сделать, но мне нужно использовать с событиями вызов кода для обновления пользовательского интерфейса, когда каждый будет сделан.

Что такоесамый простой способ сделать это?

Вот мой код.В большинстве случаев это просто копирование, вставка + изменение, пока я не получу правильную работу.

Так что в основном классе у меня есть работник по тестированию.

(я пытался использовать 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 ]

4 голосов
/ 26 января 2011

Вы получаете только последний результат, потому что вы выбрасываете 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 голос
/ 26 января 2011

Обновление : я разместил этот ответ, сосредоточив внимание именно на том, что вы пытались сделать с технической точки зрения (используйте много фоновых работников), не задумываясь о том, является ли это хорошим способом достижения ваша реальная цель. На самом деле, я думаю, вы могли бы гораздо проще достичь того, чего хотите, с помощью одного BackgroundWorker и чего-то вроде цикла Parallel.ForEach в его обработчике событий DoWork (это заботится о много мелкой работы, например, решение Дейва ).


Когда вы объявляете WithEvents TestWorker As BackgroundWorker в VB, это оборачивается чем-то вроде этого (не совсем - это просто для иллюстрации идеи):

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 в new BackgroundWorker при каждом вызове Call_Thread, вы удаляете все подключенные обработчики из объект, на который ранее ссылалось поле.

Самым очевидным решением будет просто создать новый локальный BackgroundWorker объект при каждом вызове Call_Thread, присоединить туда обработчики (используя AddHandler и RemoveHandler), а затем просто дайте ему сделать свое дело:

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

Создание работника прямо там, в методе, должно быть хорошо, если вы делаете это из потока пользовательского интерфейса, поскольку BackgroundWorker автоматически присоединяется к текущему SynchronizationContext в его конструкторе (если я правильно помню).

0 голосов
/ 26 января 2011

В идеале вы должны использовать только одного фонового работника и использовать его так:

  • Соберите всю работу, которая должна быть выполнена: в вашем случае список ServerInfo
  • Выполните работу в фоновом режиме: пропингуйте все серверы и сохраните результат
  • Отчет о прогрессе: например, после каждого pinging сервера
  • Поместить результаты обратно в DoWorkEventArgs.Result
  • Отображение результатов обратно в пользовательском интерфейсе.
0 голосов
/ 26 января 2011

Вам необходимо прикрепить TestWorker_DoWork и TestWorker_ProgressChanged к событиям DoWork и ProgressChanged в пределах Call_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)
...