运行代码时,我似乎在尝试从并行任务之一中更新GUI元素时遇到死锁。
我尝试用“ Synclock me”围绕Output函数,以确保每次仅一次任务试图更新控件。
Private Sub RunParallel(records as list(of DataRecord), ou as String)
Dim ParallelOptions As New ParallelOptions
ParallelOptions.MaxDegreeOfParallelism = 10
Parallel.ForEach(records, ParallelOptions, Sub(myrecord)
ProcessRecord(myrecord, ou)
End Sub)
Output("Done...." & vbCrLf)
End Sub
Private Sub ProcessRecord(ByVal record As DataRecord, ByVal ou As String)
'Output($"BromcomID = {record("ID")}, Forename = {record("Forename")}{vbCrLf}")
Dim ud As New UserDetails With {
.EmployeeID = record("ID"),
.SamAccountName = record("SamAccountName"),
.GivenName = record("Forename"),
.Surname = record("Surname")
}
If Not CreateUser(ou, ud) Then
'Threading.Thread.Sleep(2000)
' Output($"Error creating {ud.EmployeeID}{vbCrLf}")
End If
End Sub
Private Sub Output(ByVal s As String)
SyncLock Me
If Me.InvokeRequired Then
Invoke(Sub()
Outbox.AppendText(s)
Outbox.SelectionStart = Len(Outbox.Text)
Outbox.ScrollToCaret()
Outbox.Select()
End Sub)
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Len(Outbox.Text)
Outbox.ScrollToCaret()
Outbox.Select()
End If
End SyncLock
End Sub
提供的代码似乎可以运行,但是如果我取消注释ProcessRecord()函数中的Output调用,它将挂起并且永远不会退出Parallel.foreach
---更新 在这里提出建议和评论之后,我仍然无法正常工作。 如果我从ProcessRecord中取出所有输出,它似乎可以正常工作。但是,使用以下代码,现在似乎可以依次运行每个ProcessRecord(而不是我期望的一次运行10个),然后在最后一个之后挂起。
Output("Dispatching" & vbCrLf)
Dim ParallelOptions As New ParallelOptions With {
.MaxDegreeOfParallelism = 10
}
Parallel.ForEach(recordList, ParallelOptions, Sub(myrecord)
ProcessRecord(myrecord, ou)
End Sub)
'For Each myrecord As DataRecord In recordList
' Task.Factory.StartNew(Sub() ProcessRecord(myrecord, ou))
'Next
Output("Done...." & vbCrLf)
End Sub
Private Sub ProcessRecord(ByVal record As DataRecord, ByVal ou As String)
Dim ud As New UserDetails With {
.EmployeeID = record("ID"),
.SamAccountName = record("SamAccountName"),
.GivenName = record("Forename"),
.Surname = record("Surname"),
.DisplayName = $"{record("Forename")} {record("Surname")} (Student)"}
If Not CreateUser(ou, ud) Then
' Output($"Error creating {ud.EmployeeID}{vbCrLf}")
End If
Output($"BromcomID = {record("ID")}, Forename = {record("Forename")}{vbCrLf}")
End Sub
Private Sub Output(ByVal s As String)
If Me.InvokeRequired Then
Invoke(Sub()
Output(s)
End Sub)
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Outbox.TextLength
Outbox.ScrollToCaret()
Outbox.Select()
Outbox.Refresh()
End If
End Sub
如果我使用注释掉的Task.Factory代码,那么一切似乎都可以正常运行,除非我无法控制一次启动多少个任务,而且等不及所有任务都完成了,for循环才会启动所有任务,然后继续执行Output(“ Done ....)行。
synclock语句似乎没有任何影响。
答案 0 :(得分:0)
尝试一下
Private Sub Output(ByVal s As String)
If Me.InvokeRequired Then
Me.Invoke(Sub() Output(s))
'Me.BeginInvoke(Sub() Output(s))
Else
Outbox.AppendText(s)
Outbox.SelectionStart = Outbox.TextLength
Outbox.ScrollToCaret()
Outbox.Select()
Outbox.Refresh()
End If
End Sub
如果您有与发件箱相关的事件(例如更改文字),则可能会出现问题。经过测试的输出方法
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim nums As New List(Of Integer)
For x As Integer = 1 To 500
nums.Add(x)
Next
'because it is in a button, run from a task
Dim t As Task
t = Task.Run(Sub()
Parallel.ForEach(nums, Sub(num)
Output(num.ToString & Environment.NewLine)
End Sub)
End Sub)
End Sub
答案 1 :(得分:0)
如果您想继续使用基于Task
的方法,那么您当然可以控制一次启动多少,然后等待所有完成。它需要一些其他代码来进行手动管理。 Microsoft文档中对此进行了详细讨论:https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern
立即启动所有任务不一定是一件坏事,然后您将其留给线程池以照顾一次要运行多少个任务。
如果要更好地控制,可以使用链接中的“节流”设计。在您的“待处理”队列中,存储将自行启动Task.Run
的代表/ lambda。然后,当您从“待处理”队列中退出进入“活动”列表时,您可以在委托/ lambda上Invoke
来获得“活动”列表中的Task
和Await Task.WhenAny
这样做的潜在好处是,可以将每个顶级Task
中的工作划分为在UI线程上运行的UI工作和在线程池上运行的处理器限制的工作。
(我并不是说这对您来说不一定是最好的选择,只是在您真的想使用Task
而不是Parallel
时尝试扩展您应该做的事情)