Ganzen Sub Threadsicher im neuen Thread aufrufen

  • VB.NET

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Ganzen Sub Threadsicher im neuen Thread aufrufen

    Hallo,

    ich hab eine ganze Weile benötigt um es mit den Invoke Begriff halbwegs zu verstehen. Das mit den Delegates gelingt mir nicht so gut, daher hab ich das Problem einfach mit

    VB.NET-Quellcode

    1. LDeck.Invoke(Sub() LDeck.Width += 5)


    mehr oder weniger "gelöst". Auch ein ganzen Private Sub kann ich damit schön aufrufen

    VB.NET-Quellcode

    1. Me.Invoke(Sub() Analyse())


    Das funktioniert auch alles prima. Nur bisher benutzte ich immer BackgroundWorker.

    Was will ich eigentlich: Aus Interesse wollte ich dann mal mit ganz normalen Threads arbeiten und habe diesen auch mal so deklariert:

    VB.NET-Quellcode

    1. Dim workingthread As Thread


    So werden sie aufgerufen

    VB.NET-Quellcode

    1. workingthread = New Thread(AddressOf Workout)
    2. workingthread.IsBackground = True
    3. workingthread.Start()


    Nun müsste ich ja eigentlich einen Delegaten haben, aber wie erstelle ich einen zu z. B.:

    VB.NET-Quellcode

    1. Private Sub Workout()


    Ich stoppe immer bei Private Delegate Sub HandleWorkout() und in die Klammer müsste ja noch irgendwas. Kann mir da jemand helfen, ich glaub ich hab einen riesigen Logikfehler :( Muss ich für jeden Control einen Delegate erstellen - dann könnte ich ja noch was in die Klammer einfüllen, aber dann müsste ich wieder alles Zeile für Zeile schreiben und das wollte ich ja wie oben vermeiden.

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

    Hi
    ich glaub', dass der Begriff der Nebenläufigkeit doch noch nicht so richtig verstanden wurde. Zu Delegates beschreib' ich auch noch kurz was, dann.
    Das, was innerhalb eines Threads ausgeführt wird (die Methode, die im Konstruktor des Threads angegeben wurde), wird parallel zu dem Thread ausgeführt, der den Thread erzeugt hat. D.h. Threads sind unabhängig von UI oder sonst was, es bedeutet nur, dass Dinge parallel ausgeführt werden.

    Windows Forms haben einen Mechanismus implementiert, der darauf achtet, dass Änderungen an der UI nur in dem Thread durchgeführt werden, der das Steuerelement erzeugt hat. Und genau dafür werden dann Control.BeginInvoke und Control.Invoke benötigt. Sie erlauben es einem Thread, Aktionen auf dem Thread des Steuerelements auszuführen.
    Was daraus folgt, ist wieder klar: Du kannst sämtliche Berechnungen parallel ausführen, sodass die Oberfläche weiterhin bedienbar ist und nur die direkten oberflächenverändernden Ergebnisse im UI-Thread verarbeiten.

    Delegates kann man in VB verschieden angeben:
    - AddressOf verweist auf eine Methode. D.h. du sagst dem Thread quasi "Führe den Code aus, der da drin steht", wobei sich "da" auf die angegebene Methode bezieht (z.B. Workout)
    - Lambdaausdrücke können anstelle von AddressOf verwendet werden:

    VB.NET-Quellcode

    1. Dim t As New Thread(Sub()
    2. Dim r As Long = 0
    3. For i As Integer = 1 To Integer.MaxValue
    4. r += i
    5. Next
    6. End Sub)
    7. t.Start()


    Unterm Strich sind Delegaten nur ein Verweis auf den Code, der an irgendeiner Stelle ausgeführt werden soll. Probier ein wenig damit herum, dann steigst du schnell dahinter.
    Probier' vielleicht insbesondere mal Action, Func(Of T1, TResult) und sowas aus. Eine Stelle, an der Delegaten ständig zum Einsatz kommen, sind übrigens Events. Jeder Event-Handler ist ein solcher Delegat.

    Viele Grüße
    ~blaze~
    Geht wie bei deinem Invoke Beispiel:

    workingthread = New Thread(Sub() Workout(foo))

    In der Workout Methode kannst du mit Control.BeginInvoke() arbeiten.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen

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

    @Sneeze Noch ein Wort zum Unterschied von Invoke() und BeginInvoke().
    Bei einem Invoke() wird der Code sofort ausgefügrt, d.h., der MainThread wird gnadenlos angehalten und der invokte Code ausgeführt.
    Bei einem BefinInvoke() wird dem MainThread der Aufruf des invokten Codes sozusagen in die Queue geschoben und we wird dann ausgeführt, wenn der MainThread in die IdleLoop geht.
    Mach ein paar Tests, wo Du abwechselnd Invoke() und BeginInvoke() mit demselben Delegaten und einem Counter ausführst und Dir im MainThread den Counter in einer ListBox anhängst.
    Dann machst Du im MainThread noch einen Timer, der dasselbe tut.
    Überlege und verstehe, warum das, was da rauskommt so ist. ;)
    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!

    RodFromGermany schrieb:

    Bei einem Invoke() wird der Code sofort ausgefügrt, d.h., der MainThread wird gnadenlos angehalten und der invokte Code ausgeführt.
    Es ist der NebenThread, der bei Control.Invoke() gnadenlos angehalten wird, und nun seinerseits auf den Mainthread warten muss.
    Beachte, dass es auch Delegate.Invoke()/BeginInvoke() gibt - die sind hier aber grad nicht Thema.
    @RodFromGermany
    Habe das gerade mal mit folgendem C#-Code getestet (hatte das gerade offen, deswegen hab' ich das schnell dort eingefügt :whistling: :(

    C#-Quellcode

    1. var t = new System.Threading.Thread(() =>
    2. {
    3. for (int j = 0; j < 10000; j++)
    4. {
    5. int i = j;
    6. if (i % 2 == 0)
    7. BeginInvoke(new Action(() => _serviceListBox.Items.Add(i)));
    8. else
    9. Invoke(new Action(() => _serviceListBox.Items.Add(i)));
    10. }
    11. Invoke(new Action(() =>
    12. {
    13. int p = -1;
    14. for (int i = 0; i < _serviceListBox.Items.Count; i++)
    15. {
    16. int cp = (int)_serviceListBox.Items[i];
    17. if (cp == p + 1)
    18. {
    19. p = cp;
    20. _serviceListBox.Items.RemoveAt(i--);
    21. }
    22. }
    23. }));
    24. });
    25. t.Start();

    Und die Liste war leer. Mache ich etwas falsch oder verwechselst du Delegate.BeginInvoke bzw. .Invoke und Control.BeginInvoke bzw. .Invoke?
    Ich hätte gedacht, dass die Methoden in der Reihenfolge ausgeführt werden, in der sie das Steuerelement in die Nachrichtenschlange aufnimmt, habe das aber nie überprüft und weiß nicht mehr, ob bzw. wenn, wo ich das gelesen habe.

    Edit: Zu langsam und mir wurde der neue Beitrag nicht angezeigt, sorry. Ich lasse es aber wegen des Testcodes dennoch mal da.

    Viele Grüße
    ~blaze~
    @ErfinderDesRades Jou. Control.BeginInvoke() ist gemeint.
    @~blaze~ Ich hatte da schon mal was zu Control.Invoke() und Control.BeginInvoke() geschrieben. Gugst Du hier.
    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's mir jetzt mal durchgelesen. Schau' dir mal folgenden Link an:
    referencesource.microsoft.com/…ntrol.cs,dc49fb4c2140594f
    synchronous gibt an, ob BeginInvoke oder Invoke aufgerufen wurden, der Rest ist für beide Methoden gleich. synchronous wird in Zeile 7690 dazu verwendet, auf Abschluss des anderen Threads zu warten. PostMessage wird dazu verwendet, eine "eigens definierte" Nachricht auszuführen, der Typ der Nachricht wird erst registriert und in threadCallbackMessage abgelegt.
    Aufrufe werden alle in einer Queue gehalten und in Zeile [url=https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,7355]7355[/url] (der Aufruf erfolgt in Zeile 14031 aus WndProc heraus) ausgeführt. Hier ist ebenfalls ein ähnliches Verhalten festzustellen. Je nachdem, ob synchronous oder nicht, wird lediglich die Verarbeitung geringfügig verändert, aber tatsächlich ist das Verhalten fast nicht zu unterscheiden.
    Unter'm Strich fährt BeginInvoke lediglich mit der Ausführung fort, während Invoke wartet. Das Problem, das du in deinem Thread demonstrierst, zeigt aber eigentlich etwas anderes, nämlich dass BeginInvoke den Aufruf erst bei Verarbeitung der ersten über PostMessage eingereihten Nachricht durchführt. Invoke hingegen führt die Methode einfach sofort aus, weil es sonst zu einem Deadlock käme. Daher ist das Verhalten auch atypisch, da ohne Nebenläufigkeit ein Sonderfall eintritt. Die Nachricht kann im Übrigen erst bei Application.DoEvents verarbeitet werden oder wenn der Rücksprung in die Nachrichtenschleife geschieht. D.h. die Verarbeitung kann gar nicht in den Test-Methoden stattfinden.

    @Sneeze
    Sorry, dass das jetzt so offtopic wurde, aber es war mir wichtig, das Verhalten halbwegs zu erklären.

    Viele Grüße
    ~blaze~