Ende aller Threads

  • VB.NET

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von Kraizy.

    Ende aller Threads

    Hallo,
    habe ein kleines Problem mit Threads.
    Mal ein Ausschnitt aus meiner Klasse:

    VB.NET-Quellcode

    1. Public Class myClass
    2. Private zurzeit As Integer
    3. Private gesamt As Integer
    4. Private _list As New List(Of String)
    5. Private _thread As Threading,Thread
    6. Public Sub New()
    7. For i As Integer = 1 to 20
    8. _list.Add("blubb" & i) 'zu Testzwecken, die Einträge werden später vom Benutzer selbst hinzugefügt...
    9. Next
    10. zurzeit = 0
    11. gesamt = _list.Count()
    12. End Sub
    13. Public Sub Start(anzahl As Integer)
    14. If _thread IsNot Nothing Then
    15. If _thread.IsAlive Then
    16. [Stop]
    17. End If
    18. End If
    19. For i As Integer = 0 To anzahl -1
    20. _thread = New Threading.Thread(AddressOf _doWork)
    21. _thread.IsBackground = True
    22. _thread.Start()
    23. Next
    24. End Sub
    25. Public Sub [Stop]()
    26. If _thread IsNot Nothing Then
    27. _thread.Abort()
    28. _thread = Nothing
    29. End If
    30. End Sub
    31. Private Sub _doWork 'hier ist mein eigentliches Problem
    32. If zurzeit < gesamt Then
    33. 'hier ist dann die eigentliche Aufgabe:
    34. _list(zurzeit)...'abgekürzt
    35. 'Ende der Aufgabe. Ist hier in diesem Beispiel jedoch abgekürzt, im Original wird natürlich noch etwas damit gemacht...
    36. zurzeit += 1
    37. If zurzeit = gesamt Then
    38. [Stop]()
    39. MsgBox("Fertig!") 'das hier soll nur angezeigt werden, wenn wirklich alle _doWork-Threads fertig sind, und mit allen _list-Einträgen gearbeitet wurde
    40. Else
    41. _doWork() 'Wenn nocht nicht mit allen Einträgen aus _list gearbeitet wurde, erneut aufrufen
    42. End If
    43. End If
    44. End Sub
    45. End Class

    Ich würde es gerne so haben, dass man "Start" aufruft, und angeben kann, wie oft nun die Aufgabe gleichzeitig gemacht werden soll. Wenn ich also .Start(3) aufrufe, wird ja _doWork() 3x aufgerufen und jedesmal wird "zurzeit" um 1 erhöht. Wenn die Aufgabe also erledigt wurde, und "zurzeit" noch nicht "gesamt" erreicht hat, soll _doWork() erneut aufgerufen werden. Somit müsste ja nun _doWork() die ganze Zeit 3x gleichzeitig laufen, mit dem Unterschied, dass das nächste Item aus "_list" genommen wird.
    Das Problem ist nun aber, dass am Ende auch 3x die MsgBox angezeigt wird und ich komm nicht drauf, wie ich das ändern muss, damit sie wirklich nur einmal aufpoppt, und zwar wenn alle Aufgaben erledigt wurden.

    Achja, ein weiteres Problem ist, das immer mal wieder am Anfang die zwei selben Einträge aus _list genommen werden..also:

    Quellcode

    1. Start(5)
    2. Arbeiten mit 1. Eintrag aus _list
    3. Arbeiten mit 1. Eintrag aus _list
    4. Arbeiten mit 2. Eintrag aus _list
    5. Arbeiten mit 3. Eintrag aus _list
    6. Arbeiten mit 4. Eintrag aus _list
    7. Arbeiten mit 5. Eintrag aus _list

    Woran könnte das liegen? Ist auch nur manchmal so..

    Hoffe jemand hat das so in etwa verstanden..
    Es ist nicht sonderlich elegant, einen Thread mit Abort() zu beenden.
    Ein guter Thread wird beendet, indem man ihn veranlasst, die DoWork()-Routine zu beenden.
    Wenn Du also dort eine Endlosschleife hast, bau ein Flag ein, dass sie verlässt.
    Nach dem Setzen dieses Flags kannst Du ja ein TimeOut abwarten und danach den Thread aborten.
    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!
    Oder du machst ein Wert der auf False steht. Wenn du auf Beenden drückst (oder was auch immer) geht der Wert auf True dein/e Thread/s überprüfen denn beim laufen den Wert und wenn er auf True steht dann Exit Sub. So könnte es aussehen:

    VB.NET-Quellcode

    1. Public Dim IsGeschloseen As Boolean = False ' Der Wert
    2. Public Sub DeinThread()
    3. 'Code
    4. If IsGeschlossen = True Then Exit Sub
    5. 'Code
    6. End Sub
    7. Public Sub Beenden()
    8. IsGeschlossen = True
    9. End Sub


    So würde ich das machen
    hmm, ich sehe hier mehrere Probleme:
    1. die Abfrage ob der Thread noch arbeitet ist korrekt, aber mit [Stop] beendest Du nur den letzten der gestarteten 3 Threads, die anderen laufen schliesslich weiter.
    2. das von Dir angesprochene Problem dass verschiedene Threads das gleiche List-Element bearbeiten: hier wird die private Variable Zurzeit von 2 Threads abgerufen bevor sie erhöht wird. Das Setzen eines Synclocks würde dies verhindern.
    3. Abbruch mit Thread.Abort sollte nur bei Notfällen aufgerufen werden, und dann zumindest die TreadAbortException abgefangen werden
    4. mehr Threads als CPU's in Deinem Rechner machen keinen Sinn

    Die sauberste Lösung wäre das Übergeben eines Parameters an den jeweilig gestarteten Thread ...

    Kangaroo schrieb:

    1. die Abfrage ob der Thread noch arbeitet ist korrekt, aber mit [Stop] beendest Du nur den letzten der gestarteten 3 Threads, die anderen laufen schliesslich weiter.
    3. Abbruch mit Thread.Abort sollte nur bei Notfällen aufgerufen werden, und dann zumindest die TreadAbortException abgefangen werden

    Ok, das wusste ich gar nicht..wie soll ich diese denn dann richtig beenden?

    Kangaroo schrieb:

    2. das von Dir angesprochene Problem dass verschiedene Threads das gleiche List-Element bearbeiten: hier wird die private Variable Zurzeit von 2 Threads abgerufen bevor sie erhöht wird. Das Setzen eines Synclocks würde dies verhindern.

    Danke, werde mal danach googlen..

    Kangaroo schrieb:

    4. mehr Threads als CPU's in Deinem Rechner machen keinen Sinn

    Aber es funktioniert doch oder etwa nicht? Zumindest hats beim Probieren erfolgreich ohne Probleme geklappt, und wurde viel schneller erledigt, als jeden Eintrag einzeln durchzuarbeiten (die Aufgabe, die in _doWork erledigt werden soll, ist nicht groß, lediglich ein WebRequest, und es soll eben mit z.B. 5 Einträgen aus _list gleichzeitig gemacht werden. Hat wie schon gesagt, funktioniert)

    Kangaroo schrieb:

    Die sauberste Lösung wäre das Übergeben eines Parameters an den jeweilig gestarteten Thread ...

    Welche Parameter wären das denn?

    Kraizy schrieb:

    Ok, das wusste ich gar nicht..wie soll ich diese denn dann richtig beenden?

    erst einmal die Frage was willst Du beenden und warum überhaupt ? Willst Du alle Threads beenden ( aus irgendwelchen Gründen) so wirst Du dir alle gestarteten Threads irgendwo ( z.B. einer Liste merken müssen) um sie später auchs stopnne zu können. Momentan merkst Du Dir in der Variable _thread nur den letzten ...

    Ein Beenden mit .Abort ist ein harter Stop, ohne zu wissen an welcher Codestelle er aufhört und wieviel von der Verarbeitung der Thread schon erledigt hat. Wenn einem das egal ist, so ist es definitiv die schnellste Methode. Allerdings sollte man dann mit Try...Catch... den Fehler abfangen. Ansonsten von aussen eine bool'sche Variable setzen die den Thread auffordert an einer bestimmten Stelle zu unterbrechen.

    Kraizy schrieb:

    Danke, werde mal danach googlen..

    Ein Synclock reserviert den Zugriff auf ein Objekt (in Deinem Fall die Variable ZurZeit) exclusiv für die Dauer des Locks, z.B.

    VB.NET-Quellcode

    1. Synclock zurzeit
    2. zurzeit +=1
    3. end synclock
    So wird vermieden dass 2 Threads mit der gleichen Variable arbeiten.

    Kraizy schrieb:

    Aber es funktioniert doch oder etwa nicht?

    Mehr threads als CPU's funktionieren - klar. Nur wirst Du damit keine Zeit gewinnen, so war das von mir gemeint ...

    Kraizy schrieb:

    Welche Parameter wären das denn?

    Du kannst dem Thread beim Start einen Parameter mitgeben, hier z.B. den Index des Listelements welches er bearbeiten soll. Dafür gibt es verschiedene Möglichkeiten, hier mal ein Beispiel mit ParameterizedThreadStart

    VB.NET-Quellcode

    1. _thread = New Thread(New ParameterizedThreadStart(AddressOf doWork))
    2. _thread.Start(3)
    3. .....
    4. Sub doWork(ByVal o As Object)
    5. Dim int = CInt(o)
    6. Debug.Print("current Thread: " & int.ToString)
    7. End Sub
    Erstmal danke dafür! Werde später nochmal mein Beitrag editieren, wegen deiner Frage am Anfang.
    Jedoch klappt das mit SyncLock bei einer Int-Variable nicht..
    Der SyncLock-Operand kann nicht den Typ "Integer" haben, da "Integer" kein Referenztyp ist.

    Soll ich es dann erst in ein Object umwandeln?

    Kraizy schrieb:

    Soll ich es dann erst in ein Object umwandeln?

    Stimmt, es gehen nur Referenztypen, und nein, ich würde es nicht in ein Object umwandeln. Wenn man Synclock benutzt, so nimmt man meist eh nicht das Element welches gegen Zugriffe gesperrt werden soll, sondern ein extra angelegtes Referenzobjekt SynclockObject.

    Aber in Deinem Fall würde ich vorschlagen Du überarbeitest mal Deine Klasse oben um sie danach hier wieder zu posten. Momentan sind dort einfach zu viele Fehler enthalten.

    Übergebe den Index von aussen als Parameter, überlege Dir eine vernünftige Stop Routine und berücksichtige die anderen Hinweise in diesem Post.
    4. mehr Threads als CPU's in Deinem Rechner machen keinen Sinn

    Wieso macht das keinen Sinn?
    Das würde ich gerne mal näher beschrieben wissen.
    Das ist doch völlig abhängig davon, was die einzelnen Threads machen sollen...
    Das ist meine Signatur und sie wird wunderbar sein!

    Mono schrieb:

    Wieso macht das keinen Sinn?
    Das würde ich gerne mal näher beschrieben wissen.
    Das ist doch völlig abhängig davon, was die einzelnen Threads machen sollen...

    Das war ein Kommentar zu dem von Kraizy geposteten Code, in dem er seine Listelemente in parallelen Threads (mit gleicher Verarbeitungslogik) bearbeitet. Wie oben gepostet, spart dies in der Regel nur Zeit wenn die Anzahl der CPU's nicht überschritten wird.

    Was war jetzt an dieser Aussage unverständlich Mono ?
    Er hat ja nicht geschrieben, was die Threads genau machen.
    Daher hat diese Aussage einen generellen Charakter.

    Wenn die Threads zB Daten übers Netzwerk abrufen oder ähnliches, wobei ggf. Timeouts oder "Warten" auf eine Antwort vorkommen, wird man definitiv mit mehr Threads schneller sein als mit genau der Anzahl der CPU's(bzw. Kerne)
    Auch wenn im Framework viele solcher Abfragen auch eine asynchrone Aufruf Methode mitbringen, die ich der Erstellung mehrerer Threads bevorzugen würde.
    Man kann also in meinen Augen nicht generell sagen, das maximal soviele Threads wie CPU's besser oder genauso schnell sind wie mehrere.
    Dies gilt lediglich für Aufgaben, die alle komplett auf deiner CPU berechnet werden.

    Oder sehe ich das falsch ?
    Das ist meine Signatur und sie wird wunderbar sein!
    Die Threads sollen mit den Einträgen aus der Liste einen WebRequest senden. Beim Testen mit 5 gleichzeitigen Threads wurde dies viel schneller erledigt, als wenn ich jeden Request einzeln versende und erstmal auf die Antwort warte.

    Zu dem Synclock: Kann ich da nicht die Liste selbst nehmen: Also:

    VB.NET-Quellcode

    1. ...
    2. Synclock (_list)
    3. zurzeit += 1
    4. End Synclock
    5. ...

    Hab's damit oft probiert und bis jetzt wurde kein einziges Mal der gleiche Eintrag ausgewählt (ohne Synclock spätestens nach dem 5. Versuch).

    Kraizy schrieb:

    Zu dem Synclock: Kann ich da nicht die Liste selbst nehmen

    Kannst Du natürlich, so wie jedes andere Referenzobjekt, da alle Threads in den Wait-Status gehen, sobald 1 Thread das Objekt geblockt hat.

    Was mir bei Deiner Logik oben nur auffällt: Du hast erst einen langen Verarbeitungsteil , bevor Du die variable zurzeit um 1 erhöhst. Normalerweise würde ich erwarten, dass alle schnell nacheinander gestarteten Threads deshalb das gleiche Element bearbeiten. In diesem Fall lieber gleich am Anfang des Threads per Synclock um 1 erhöhen.

    Wenn Du übrigens alle Listelemente durchgehen möchtest, könntest Du auch mit einem Event arbeiten, bei dem wenn 1 Thread fertig ist gleich der nächste gestartet wird.

    Oder Du arbeitest gleich mit einem asynchronen WebRequest.
    Ich würde dir auch zum Asynchronen Webrequest raten.

    Außerdem würde ich Synclock in diesem Fall nicht verwenden, da es für atomare Operationen die Interlocked Class gibt.
    In deinem Fall also

    VB.NET-Quellcode

    1. Interlocked.Increment(zurzeit)


    Dies ist schneller als Synclock/Monitor

    Gruss Mono
    Das ist meine Signatur und sie wird wunderbar sein!
    Ich glaube das mit async WebRequests wird nicht klappen, weil ich eine API benutze und bloß die Funktionen aufrufe. Diese führen dann die Requests durch, ich erstelle sie somit nicht selbst.
    Also ich würde das ja gerne so mit Threads machen, sodass danach der nächste Thread automatisch gestartet wird mit dem nächsten Eintrag (das war ja auch mein Versuch mit dieser Klasse von oben), jedoch ist das wohl nicht besonders gelungen...
    Könntest du mir vielleicht ein kleines Beispiel zeigen, wie man das richtig machen könnte?
    Ich weiß ja nicht wie die Requests aussehen, weil ich z.B. nur die Funktion "Login" mit den Daten aus_list aufrufe. Danach wird ein Request abgeschickt und ich bekomme nun z.B. True oder False zurück. Wenn True, dann soll eine weitere Funktion der DLL aufgerufen werden, ansonsten überspringen (und sofort mit dem nächsten Eintrag aus _list anfangen zu arbeiten). Und das würde ich eben 5x gleichzeitig machen bis alle Einträge aus _list durch sind.

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