Mehrere Threads aufrufen und Daten in den Hauptthread übergeben

  • VB.NET

Es gibt 23 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

    Mehrere Threads aufrufen und Daten in den Hauptthread übergeben

    Hallo,

    ich versuche mal den Sachverhalt im Detail zu schildern.

    Ich prüfe 3D-Modelle auf Kollision. Funktioniert auch perfekt.

    In Form1 habe ich ein OpenGL-Fenster, also die Visualisierung. In der Form1 ist auch eine Liste deklariert als: IntersectionVolumes As List(Of Mesh).

    Für die Kollisionsprüfung habe ich:

    1. ContourPath as List(Of Geometry3D) ==> Eine Liste mit Linien, Kreisausschnitten oder Vollkreis und Kurven
    2. StaticEntities As List(Of Mesh) ==> Eine Liste mit 3D-Modellen welche nicht Transformiert werden
    3. DynamicEntites As List(Of Mesh) ==> Eine Liste mit 3D-Modellen welche entlang des ContourPath Transformiert werden. Bei jeder Transformation erfolgt eine Kollisionsprüfung zwischen StaticEntities und DynamicEntities.

    Je nach dem wie groß das zu bearbeitende Bauteil ist und je nach dem wie hoch die Genauigkeit sein soll, kann es passieren das der ContourPath aus bis zu 6000 3D-Punkten oder mehr besteht.

    Ich würde gerne den ContourPath auf z.B. 3 Threads aufteilen.

    Thread1 soll 0 - 1999 rechnen.
    Thread2 soll 2000 - 3999 rechnen.
    Thread3 soll 4000 - 6000 rechnen.

    Den Threads muss man StaticEntities, DynamicEntites und einen Teil des ContourPath übergeben

    Schema der Kollisionsprüfung
    ==========================

    Dim iv As New List(Of Mesh)

    For i = 0 To ContourPath.Count-1

    Dim t As Transformation = GetTransformation(ContourPath(i)) ==> Diese Funktion ist in einem Modul

    DynamicEntities.TransformAll(t)

    Dim cp As CollisionDetection3D = New CollisionDetection3D(StaticEntities,DynamicEntites)
    cp.Dowork()

    If cp.IntercetionVolumes IsNot Nothing Then iv.AddRange(cp.IntersectionVolumes.Mesh)

    Next

    Jetzt soll der Thread die iv-Liste in den IntersectionVolumes ( HauptThread ) übergeben.

    HauptThread.IntersectionVolumes.AddRange(iv)

    Thread beenden.

    Das Programm soll erst dann weiter laufen wenn alle zusätzlichen Threads geschlossen sind.


    Puhhhhh :D :D :D

    Besten Dank.

    DragsTrail schrieb:

    Ich würde gerne den ContourPath auf z.B. 3 Threads aufteilen.

    Thread1 soll 0 - 1999 rechnen.
    Thread2 soll 2000 - 3999 rechnen.
    Thread3 soll 4000 - 6000 rechnen.
    Sieh Dir mal Parallel.For und Parallel.ForEach an.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @DragsTrail Dann programmiere das ganze zunächst ohne Threads.
    Wenn der Algorithmus läuft, denken wir über Nebenläufigkeit und Parallelisierbarkeit nach.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @DragsTrail Dann fang an mit Parallel.For und Parallel.ForEach.
    Schneller bekommst Du es nicht.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Also ich hab das so gelöst:

    VB.NET-Quellcode

    1. ' ##################################################
    2. ' 1600 Kollisionsprüfungen durchführen
    3. ' ##################################################
    4. Dim icc As New List(Of Entity)
    5. Dim myOptions As ParallelOptions = New ParallelOptions()
    6. myOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount ' Mann kann einegebn was man will, schneller wird es nicht ???????????
    7. Dim x = 120
    8. Dim y = 120
    9. Dim z = 120
    10. Parallel.For(0, 21, myOptions, Sub(i)
    11. Parallel.For(0, 21, myOptions, Sub(k)
    12. Parallel.For(0, 4, myOptions, Sub(j)
    13. DynamicEntites.TransformAll(i * x, k * y, j * z)
    14. Dim cd As CollisionDetection = New CollisionDetection(cubes, DynamicEntities, Model1.Blocks, False, CollisionDetection3D.collisionCheckType.Geometry3D)
    15. cd.DoWork()
    16. If cd.Result.Count > 0 Then
    17. cd.ComputeIntersectionVolume()
    18. For kk = 0 To cd.Result.Count - 1
    19. collisionSections.AddRange(cd.Result(kk).CollisionItems)
    20. Next
    21. End If
    22. End Sub)
    23. End Sub)
    24. End Sub)
    25. ' ##################################################
    26. ' Alle Kollisionen in das 3D-Fenster visualisieren
    27. ' ##################################################
    28. Parallel.For(0, 1, myOptions, Sub(i)
    29. For i = 0 To collisionSections.Count - 1
    30. If TypeOf collisionSections(i) Is Mesh Then
    31. icc.Add(New MyMeshOnTop(DirectCast(collisionSections(i), Mesh)))
    32. End If
    33. Next
    34. Model1.Entities.AddRange(icc)
    35. Model1.ZoomFit(120)
    36. Model1.Invalidate()
    37. End Sub)


    Ergebnis: 9166 Kollisionen / 20.86 Sec mit Visualisierung( 4.05 Sec ).

    Gibt es einen anderen Lösungsansatz?

    CodeTags korrigiert ~VaporiZed
    Bilder
    • Cubes.png

      488,47 kB, 1.266×746, 52 mal angesehen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

    Lösungsansatz 2 von mir:

    Quellcode

    1. Dim gcx As New System.Threading.Thread(AddressOf GreenCubesCheck)
    2. gcx.Start()
    3. Dim rcx As New System.Threading.Thread(AddressOf RedCubesCheck)
    4. rcx.Start()
    5. Dim bcx As New System.Threading.Thread(AddressOf BlueCubesCheck)
    6. bcx.Start()
    7. Dim ycx As New System.Threading.Thread(AddressOf YellowCubesCheck)
    8. ycx.Start()


    Einfach 4 fixe Threads.

    Mit parallel.for im Schnitt 21Sec
    Mit for im Schnitt 24Sec
    Mit 4 Threads ===== 13 Sekunden =====

    Hab noch probiert mit 2, 6, 8 und 12 Threads.

    Das beste Ergebnis sind 4 Threads.

    Das ist insofern interessant weil System.Environment.ProcessorCount = 12.
    Die CPU Auslastung geht auf max. 40% und das nur kurzzeitig. Im Schnitt 33%.

    Müsste das beste Ergebnis nicht bei 12 Threads leigen?

    Wenn jemand noch einen anderen Vorschlag hat, bitte melden.

    Frohe Weihnachten!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „DragsTrail“ ()

    also ich find den Code aus post#7 schon recht abenteuerlich
    mit seine 3 verschachteltne Schleifen komme ich auf 22 * 22 * 4 Threads - das kann imo nix bringen.
    Probierma nur die äussere Schleife mit parallel - und was darinnen ist, normal.


    Und der Code ab #43 ist noch komischer: Da wird ein i reingegeben in die Sub, aber innen durch einen Zähler überschrieben - das würde ich auch erstmal ent-paralellisieren.
    @ErfinderDesRades Genau.
    @DragsTrail Genau ein Parallel.For, der Rest mit For.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Jetzt hab ich das in allen Variationen probiert.

    Parallel , For, For
    Parallel, Parallel, For
    Parallel, Parallel, Parallel

    IMMER das gleiche. Knapp 21 Sekunden.

    Jetzt ist mir noch etwas interessantes aufgefallen.

    Die Form wo die Simulation stattfindet rufe ich wie folgt auf.

    Dim f as Frm_Simulation = New Frm_Simulation()
    f.Show(Me)

    Wenn ich die Form das ALLER erste mal Aufrufe dauert die Simulation 21 Sekunden. Jetzt schließe ich das Fenster mit X und starte es neu. Jetzt ändern sich die Rechenzeiten wie folgt:

    1mal Starten 21sec
    2mal Starten 42sec
    3mal Starten 63sec
    4mal Starten 84sec

    Das komplette Programm schließen und neu starten ==> 21 Sec

    ??????????????????????????????????????????????????

    Alles was in Modulen und Klassen ausserhalb der Frm_Simulation liegt dispose ich per Code in Event Frm_Simulation.Disposed. Alles was mit der Form selber zusammenhängt muss doch die Form selber machen?

    Was ist da los? ;(
    Da sich die Dauer jedesmal um 21 sekunden erhöht, würde ich vermuten das du jedesmal die gleiche Anzahl statischer oder dynamischer Objecte hinzufügst, wobei die aus dem Lauf vorher noch da sind. Wobei ich deine Art zu testen fragwürdig finde. Sind schon ziemlich viele Objekte welche du transformierst und auf kollision testest. Also 1600 cubes sind schon einiges. Um feststellen zu können, was so lange dauert, mess die Zeit die das transformieren braucht.

    'startzeit nehmen
    DynamicEntites.TransformAll(i * x, k * y, j * z)
    'ausrechnen aktuellezeit - startzeit.

    dann auch wie lange die kollisionDetektion dauert. Möglicherweise ist da noch was zu machen, CollisionDetection3D.collisionCheckType.Geometry3D wie genau testest du(da können wir nur raten)? AABB(Axis-Aligned-Bounging-Box)? Wenn nicht, teste mal damit, mit AABB kollsisionDetektion habe ich gute Erfahrungen gemacht.
    developer.mozilla.org/en-US/do…es/3D_collision_detection

    DragsTrail schrieb:

    Jetzt hab ich das in allen Variationen probiert.
    Ist der Code, den Du mit Parallel.For aufrufst, dafür optimiert?
    Ich sage es noch einmal:
    Es kann nur ein Parallel.For geben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Takafusa

    Deine Vermutung mit den Objekten war voll richtig. Ich habe das Event.Disposed zich mal angeschaut und immer wieder übersehen, das ich eine Liste nicht zurücksetze. ;(

    Die Transformation AABB ( BoundingBox des ganzen Entities) alleine ist für mich zu ungenau. Ich Programmiere kein Spiel, sondern eine Simulation für Bearbeitungsmaschinen.
    Der CheckType.Geometry3D ist in Vergleich zur Genauigkeit die schnellste Möglichkeit auch bei Komplexen 3D-Geometrien eine Kollision zu erkennen. Es ist eine Kombination aus BoundingBox und einer 3D-Matrix ==> das Ganze Volumenmodel wird mit der Anzahl x von BoundungBoxen mit einer bestimmten Dimension ( a^3) unterteilt und diese werden dann auf Kollision geprüft.

    Die Zeiten:

    TransformAll ==> 2%
    Kollisionsüberprüffung ==> 50%
    IntersectionVolumes ==> 48%


    @RodFromGermany

    Es ist nur EINE Parallel.For.
    Jetzt erklär mir doch mal bitte, was bedeutet für dich "DAFÜR OPTIMIERT?"

    Poste doch mal ein "optimiertes" Beispiel welches den Sinn meiner Aufgabenstellung erfüllt.

    DragsTrail schrieb:

    was bedeutet für dich "DAFÜR OPTIMIERT?"
    Der Code muss parallelisierbar sein.
    Folgendes Beispiel funktioniert NICHT mit Parallel.For:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Threading.Tasks
    2. Public Class Form1
    3. Private Summe As Integer
    4. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    5. Summe = 0
    6. For i As Integer = 0 To 1023
    7. SubXY(i)
    8. Next
    9. Label1.Text = Summe.ToString()
    10. End Sub
    11. Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    12. Summe = 0
    13. Parallel.For(0, 1924, AddressOf SubXY)
    14. Label2.Text = Summe.ToString()
    15. End Sub
    16. Private Sub SubXY(i As Integer)
    17. For j As Integer = 0 To 1023
    18. Summe += j
    19. Next
    20. End Sub
    21. End Class
    Es kommen bei Parallel.For immer andere Summen heraus!
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich hab mich jetzt auch mal am Parallelen verarbeiten versucht. Am anfang war eine 3 Fache For Schleife im GUI Thread ~11 Sekunden. MIt Parallel.ForEach ~55 Sekunden.

    Habe mir dann das hier mal angeschaut:
    docs.microsoft.com/de-de/dotne…data-and-task-parallelism

    Nach ein wenig mehr rumprobiererei habe ich Tasks eingebaut, habe mich gewundert warum die letzte Ausgabe im Ausgabefenster nicht die Zeit war, wie der Code vermuten lies, da hab ich mir einfach von einer MessageBox die Zeit anzeigen lassen, 3 Fach Loop im Task und auch Paralell Foreach im Schnitt 1.3 Sekunden, die tasks sind durch und VS schreib noch eine Weile fleissig weiter obwohl der Task schon durch ist.

    Wäre das in dem Sinne optimiert? (Ja ist es) Ich denke für meinen ersten Versuch nicht schlecht. Wenn man nichts im GUI thread(oder so wenig wie nötig) macht oder das Studio nicht in die Ausgabe schreiben lässt sollte das gut sein, dafür das ich von 55 auf 1.3 Sekunden kam, auch wen ich durch das Debug.WriteLine noch einen deadlock hatte solange VS noch in die Ausgabe schrieb.


    Wobei ich jetzt gerade doch denke das ist mist. Ich denke ich bleibe weiterhin beim MultiThreading. War nur so schenll durch wegen dem BeginInvoke, mit Invoke dauerst schon wieder deutlich länger beim paralell..

    VB.NET-Quellcode

    1. Imports System.Threading.Tasks
    2. Public Class Form1
    3. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    4. Dim startTime As DateTime = DateTime.Now
    5. Task.Factory.StartNew(Sub()
    6. For i As Integer = 0 To 119
    7. For k As Integer = 0 To 119
    8. For z As Integer = 0 To 3
    9. If k Mod 2 = 0 Then
    10. Dim r = i + k + z
    11. BeginInvoke(Sub() Debug.WriteLine(r))
    12. End If
    13. Next
    14. Next
    15. Next
    16. MessageBox.Show((DateTime.Now - startTime).TotalSeconds.ToString())
    17. End Sub)
    18. End Sub
    19. Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    20. Dim startTime As DateTime = DateTime.Now
    21. Task.Factory.StartNew(Sub()
    22. Parallel.ForEach(Iterate(120, 120, 4), AddressOf SubXY)
    23. MessageBox.Show((DateTime.Now - startTime).TotalSeconds.ToString())
    24. End Sub)
    25. End Sub
    26. Private Iterator Function Iterate(_i As Integer, _k As Integer, _z As Integer) As IEnumerable(Of Integer())
    27. For z As Integer = 0 To _z - 1
    28. For i As Integer = 0 To _i - 1
    29. For k As Integer = 0 To _k - 1
    30. Yield {i, k, z}
    31. Next
    32. Next
    33. Next
    34. End Function
    35. Private Sub SubXY(ikz() As Integer)
    36. If ikz(1) Mod 2 = 0 Then
    37. BeginInvoke(Sub()
    38. Debug.WriteLine(ikz(0) + ikz(1) + ikz(2))
    39. End Sub)
    40. End If
    41. End Sub
    42. End Class

    Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „Takafusa“ ()

    Hi Taka!

    Ich verstehe nicht, was dein Code tut, aber bei Nebenläufigkeit sollte man generell gucken, grosse Datenpakete zu erzeugen, welche dann zur Verarbeitung an den MainThread gegeben werden. Nicht für jeden Pups invoken.
    Jeder Thread-Wechsel - sei es .Invoke oder .BeginInvoke - ist teuer und langsam.
    (.BeginInvoke ist natürlich vorzuziehen, wenn möglich - allerdings bei massenhaften (Begin)Invokes werden die sich ja iwie überstürzen - kann auch nix gutes werden)

    Dein Code scheint aber genau das zu tun - zumindest button1_Click

    Was bei parallel.For im einzelnen passiert blicke ich nicht durch - aber sieht mir nach demselben Problem aus.
    Hey ErfinderDesRades, tun sollte der Code nicht viel, wollte mich einfach mal dran versuchen und schauen wie das zu machen ist, wie auch schauen wie man es machen muss, um einen Vorteil dadurch zu haben. RodFromGermany sagte ja es muss dafür optimiert sein, hab sein Beispiel auch probiert und war erstaunt, das das Ergebniss wirklich jedesmal ein anderes war. Nach kurzer überlegeung war mir klar, das man wohl 3 Werte reingeben muss in die Sub. Daher auch die Iterator Funktion und ForEach statt For, um einen Integer Array zu bilden und in die Sub zu bekommen.

    Die Werte stimmten, keine Überraschung wie in RodFromGermany's Beispiel. Mir war zwar bewusst was der Unterschied zwischen Invoke und BeginInvoke ist, aber wie sich bei massenhaften aufrufen auswirken kann, ist mir heute klar geworden, hatte noch nie so eine Situation geschaffen. Nachdem du gesagt hast, man soll große Datenpackete erzeugen, denke ich mir, ich mach demnächst noch mal einen Versuch, deutlich weniger durchläufe und ausreichend zu tun, den die vielen kurzen Threads dürften auch einiges an Overhead erzeugen, das wird so vermute ich der Grund sein, warum Paralell nicht immer schneller ist, als einfache Loops.

    Also im ganzen war es lehrreich, ich glaube ich habs jetzt soweit auch verstanden. Der TE kann nun auch mal probieren wie sich das bei seiner KollisionsDetektion verhält, da wird ja mehr zu machen sein. Jedenfalls nur ein Paralell.For anstatt 3, was RodFromGermany ja schon gesagt hat. Ob das nun beim TE den Ablauf beschleunigen kann, mal abwarten ob der TE das probiert und uns das Resultat mitteilt. Wobei ich glaube der Overhead durch die sehr vielen Threads überwiegt in dem Fall auch und verlangsamt es.

    Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „Takafusa“ ()