Untersuchungen zu Invoke() und BeginInvoke() - Reihenfolge der Abarbeitung

    • VB.NET

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von OlafSt.

      Untersuchungen zu Invoke() und BeginInvoke() - Reihenfolge der Abarbeitung

      Moin Leute,
      die Verwendung von BeginInvoke() an Stelle von Invoke() kann zu unerwarteten Ergebnissen führen, ich möchte Euch an meinen Untersuchungen teilhaben lassen.
      Das Beispiel arbeitet ohne Threads, dafür ist es sehr einfach gehalten und ohne Kommentare verständlich.
      Ablauf: An eine String-List werden Zahlen (.ToString) angehängt, die Reihenfolge der Aufrufe und die Reihenfolge der List-Elemente Ziel der Untersuchung.
      Kern sind 2 Routinen, die äußerlich (fast) gleich aussehen, sie unterscheiden sich lediglich in der verwendeten Invoke-Prozedur, einmal Invoke(), einmal BeginInvoke().
      Getestet wird mit 3 Button-Click-Routinen, die je eine der Testroutinen aufrufen. Bei der dritten Button-Routine hab ich noch ein Application.DoEvents() reingeschrieben, dieser dritte Test kam als Folge der Ergebnisse der zweiten Button-Click-Routine hinzu.
      Der Ablauf ist gleich, die Liste wird gelöscht, es werden die Testroutinen aufgerufen und es wird die aufgebaute Liste ausgegeben.
      GUI: Form mit 3 Button und einer RichTextBox, alles mit Default-Properties:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Class Form1
      2. Private MyList As New List(Of String)
      3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      4. Me.MyList.Clear()
      5. Me.MyList.Add("Invoke:")
      6. Me.Test1()
      7. Me.RichTextBox1.Lines = Me.MyList.ToArray()
      8. End Sub
      9. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
      10. Me.MyList.Clear()
      11. Me.MyList.Add("BeginInvoke:")
      12. Me.Test2()
      13. Me.RichTextBox1.Lines = Me.MyList.ToArray()
      14. End Sub
      15. Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
      16. Me.MyList.Clear()
      17. Me.MyList.Add("BeginInvoke + DoEvents:")
      18. Me.Test2()
      19. Application.DoEvents()
      20. Me.RichTextBox1.Lines = Me.MyList.ToArray()
      21. End Sub
      22. Private Sub Test1()
      23. Me.AddNumber(1)
      24. Me.Invoke(Sub() AddNumber(2))
      25. Me.AddNumber(3)
      26. Me.Invoke(Sub() AddNumber(4))
      27. Me.AddNumber(5)
      28. Me.Invoke(Sub() AddNumber(6))
      29. Me.AddNumber(7)
      30. End Sub
      31. Private Sub Test2()
      32. Me.AddNumber(1)
      33. Me.BeginInvoke(Sub() AddNumber(2))
      34. Me.AddNumber(3)
      35. Me.BeginInvoke(Sub() AddNumber(4))
      36. Me.AddNumber(5)
      37. Me.BeginInvoke(Sub() AddNumber(6))
      38. Me.AddNumber(7)
      39. End Sub
      40. Private Sub AddNumber(number As Integer)
      41. Me.MyList.Add(number.ToString())
      42. End Sub
      43. End Class
      Dies ist der Output:
      Bei der Verwendung von Invoke() stimmt die Ausgabereihenfolge mit der Aufrufreihenfolge überein.
      Bei der Verwendung von BeginInvoke() stimmt die Ausgabereihenfolge nicht mit der Aufrufreihenfolge überein, die Elemente, die mit BeginInvoke() hinzugefügt werden, werden nicht angezeigt.
      Bei der Verwendung von BeginInvoke() stimmt die Ausgabereihenfolge nicht mit der Aufrufreihenfolge überein, die Elemente, die mit BeginInvoke() hinzugefügt werden, werden angezeigt.
      Was passiert?
      Invoke() lässt den Invoke-Code im GUI-Thread ausführen, wenn er aufgerufen wird.
      BeginInvoke() lässt den Invoke-Code im GUI-Thread ausführen, bevor die GUI in den Idle-Zustand zurückkehrt. Da dies im Beispiel erst nach dem Anzeigen der List in der RichTextBox der Fall, die mit BeginInvoke() angehängten Werte werden nicht angezeigt.
      Diese Werte sind allerdings vorhanden, überzeugt Euch mit folgender Erweiterung davon:

      VB.NET-Quellcode

      1. Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
      2. Me.RichTextBox1.Lines = Me.MyList.ToArray()
      3. End Sub

      BeginInvoke() und DoEvents() lässt den Invoke-Code im GUI-Thread ausführen, wenn DoEvents() aufgerufen wird, deswegen ist die Aufruf-Reihenfolge verschieden von der Anzeigereihenfolge.

      Überprüft genau, welchen Code Ihr bei Invoke() und BeginInvoke() aufruft, denn das Resultat kann anders ausfallen, als Ihr es erwartet.
      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:

      Das Beispiel arbeitet ohne Threads
      Hmm - üblicherweise macht Control.BeginInvoke() aber grade in Threading-Szenarien überhaupt Sinn.
      Und in solchen würde man keinesfalls die Anweisungen zum Befüllen der Liste invoken, sondern invoken würde man die Anweisungen zum Updaten des Guis, nachdem die Liste befüllt ist.
      Also so ists zumindest in Threading-Szenarien.

      Und sparsam damit umgehen. Mehrere Invokes hintereinander sind ein Design-Fehler, wenn man die Anweisungen auch in einem einzigen Invoke unterbringen kann.

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

      Ich denke auch das BeginInvoke "works as intended". BeginInvoke-Methoden laufen ja, wie schon erwähnt, erst, wenn der GUI-Thread im Idle ist. Die Anzeige erfolgt aber noch während die GUI beschäftigt ist - keine Chance für die Invokes also. Das erklärt auch, warum es mit DoEvents plötzlich klappt. Dann ist die GUI im Idle, ergo haben die Invokes auch die Chance zu laufen, bevor es zur Anzeige kommt.