需要解决我的parallel.foreach

时间:2019-05-21 14:42:33

标签: vb.net multithreading

运行代码时,我似乎在尝试从并行任务之一中更新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语句似乎没有任何影响。

2 个答案:

答案 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来获得“活动”列表中的TaskAwait Task.WhenAny

这样做的潜在好处是,可以将每个顶级Task中的工作划分为在UI线程上运行的UI工作和在线程池上运行的处理器限制的工作。

(我并不是说这对您来说不一定是最好的选择,只是在您真的想使用Task而不是Parallel时尝试扩展您应该做的事情)