我一直在拉着我的头发试图让它发挥作用。如果我在调试器中单步调试代码,那么一切都很好。
我的问题是,如果我只是运行它,只有最后一个任务响应。我猜我正在覆盖背景工作或其他什么。我确信我做了一些错误但是我的代码现在很麻烦,因为我在搜索时尝试了很多方法。我知道线程池和.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
答案 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
事件处理程序中的DoWork
和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
当您意识到这一点时,很明显,每次拨打TestWorker
时,将 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
只要你在UI线程中这样做,就可以在方法中创建工作者,因为BackgroundWorker
会自动附加到其构造函数中的当前SynchronizationContext
(如果我没记错的话)
答案 2 :(得分:0)
您需要将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)
答案 3 :(得分:0)
理想情况下,您应该只使用1个背景工作者并像这样使用它: