2011-01-26 38 views
0

私はこれを動作させるために私の髪を引き出しています。デバッガでコードをステップ実行すれば、すべてうまくいく。VB.Net複数のバックグラウンドワーカー - 最後のタスクが完了しました

私の問題は、実行したばかりの場合、最後のタスクだけが応答することです。私はバックグラウンドの仕事や何かを上書きしていると思います。私はいくつかのことを間違っていると確信していますが、検索中に多方面で試してみると、私のコードは面倒です。私はスレッドプールと.Net 4.0タスクを知っていますが、私が必要とすることをやるのは苦労しています。

基本的に私はコンピュータとpingのリストをとり、その稼働時間をチェックしてレポートを返すプログラムを作成しています。

これはUIスレッドで正常に動作します(明らかに画面がロックされます)。私はバックグラウンドワーカーにこれをやってもらうことができますが、それはそれぞれのコンピュータを1つずつ実行し、画面は応答しますが、それでも時間がかかります。

私の答えは、新しいバックグラウンドワーカースレッドを起動する各サーバーの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 
+0

Ping.SendAsync()の使用を検討しましたか? –

答えて

3

TestWorker = New System.ComponentModel.BackgroundWorkerに電話するたびにBackgroundWorkerが吹き飛ばされているため、最後の結果しか得られません。処理は非同期的に行われているため、前の作業が終了する前にこの行がループ内で複数回呼び出されています。

次のようなものが動作する可能性があります。(申し訳ありませんが、私の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 
+0

デイブ、私はそのようなものだと思った。私はあなたがTestworker()としてバックグラウンドワーカーを宣言することができることを見ましたが、私が必要とする「WithEvents」でそれをやり遂げることはできません。あなたの応答を待っています。うまくいけば、私にそれを行う正しい方法を示すことができます。 – Jasin

+0

@ジャシン:あなたのワーカーのイベントを処理するために* WithEventsは必要ありません。 VBでは、これを動的に行う方法は、 'AddHandler'と' RemoveHandler'を使うことです。例については私の答えを見てください。 –

+0

うわー。最初は、あなたはただ1人のバックグラウンドワーカーを使っていると思った。しかし、あなたはバックグラウンドワーカーを使ってpingにスレッドを呼び出すことになります。それを理解するために少し私を取るが、それは有用であり、素晴らしい作品です。ただ私のコードにそれを含める必要があります。 ありがとうございました – Jasin

0

あなたはCall_ThreadDoWorkProgressChangedイベントにTestWorker_DoWorkTestWorker_ProgressChangedを添付する必要があります。私はまだコードの残りの部分を調べていませんが、それが今は何もしていない理由です。

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) 
+0

アダム、ありがとう私はそれを試みたが、私は2つがイベントであり、直接上げることができないと言う2つのエラーを取得します。リストの最後のコンピュータが正しく表示されます。 – Jasin

+0

Adam、VBでは、ハンドラを追加/削除する構文は、C#で使用される '+ ='演算子とは対照的に 'AddHandler'と' RemoveHandler'を使用しています(これは@Jasinがあなたのコードで混乱する理由です)。 –

+0

@ジャシン:申し訳ありません。私はあなたのVB.NETコードをコピーしたが、何らかの理由で私はC#でイベントを記述するコードを書いた。これは今あなたのために働くはずです。 @ダン:ダー、それに感謝!私は何を考えていたのか分からない! –

0

理想的にはあなただけの1のBackgroundWorkerを使用して、このようにそれを使用する必要があります。

  • 実行する必要があるすべての作業を組み立て:あなたのケースでServerInfo
  • のリストがで仕事をします背景には:すべてのサーバーにpingを実行し、結果を保つ
  • レポートの進捗状況:各サーバがDoWorkEventArgs.Result
  • 入れ結果バックpingを実行した後、例えば
  • 結果をUIに表示します。
+0

ジッピー、ありがとう、私はそれに頼る必要があるかもしれません。一度に100台程度のサーバーからpingと情報を取得する予定であるため、複数のスレッドを使用したいので、同時に複数のpingを実行してプロセスを高速化することができれば嬉しいです。 – Jasin

1

更新:私は多くを使用します(あなたが技術的な観点からやろうとしたまさにに焦点を当て、この答えを投稿しましたあなたが実際にの目的を達成する良い方法であるかどうかを十分に考えずに、実際には、BackgroundWorkerDoWorkイベントハンドラに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を呼び出すたびに新しいBackgroundWorkerからTestWorkerを設定、以前のフィールドが参照するオブジェクトから接続されているすべてのハンドラを削除しています。

最も明白な修正は、単に、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 

BackgroundWorkerがコンストラクタの現在の(自動的に覚えている場合)SynchronizationContextに自動的にアタッチするので、メソッド内にワーカーを作成するのは、UIスレッドから行う限り、問題ありません。

+0

Dan、ありがとうAddHandlerイベントを見ていましたが、値を渡して受け取る方法が見つかりませんでした。 – Jasin

+0

@Jasin: 'AddHandler'はこれを実行します:ハンドラを追加します。あなたは、(あなたの質問にあるコードのように) 'RunWorkerAsync'を呼び出してイベントを発生させ、同じように値を渡します。 –

+0

Danだから私はRunWorkerAsyncを意味する(ここでは、関数が通常は{私の場合はカスタムクラス}を期待しているという値を含めることができます)。 – Jasin

関連する問題