Serial Port Monitor für VB.net // ist meine Lösung stabil // kenne mich mit invoke und delegaten nicht gut aus

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von nogood.

    Serial Port Monitor für VB.net // ist meine Lösung stabil // kenne mich mit invoke und delegaten nicht gut aus

    Hallo,

    wieder mal ne Anfängerfrage.
    Konkret bastele ich daran, eine Drehknopf der an einem Arduino hängt über VB auszulesen mittels des Serial Ports (USB).
    (2x button 2x Combobox 1x Richtextbox)

    Kann ich das so machen ?

    VB.NET-Quellcode

    1. Imports System
    2. Imports System.ComponentModel
    3. Imports System.Threading
    4. Imports System.IO.Ports
    5. Public Class Form1
    6. Private myPort As String()
    7. Private noDevicetxt As String = "No Device found"
    8. Delegate Sub SetTextCallback(ByVal txt As String)
    9. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    10. Me.CenterToScreen()
    11. myPort = IO.Ports.SerialPort.GetPortNames()
    12. CbxBaud.Items.Add(9600)
    13. CbxBaud.Items.Add(19200)
    14. CbxBaud.Items.Add(38400)
    15. CbxBaud.Items.Add(57600)
    16. CbxBaud.Items.Add(115200)
    17. For i = 0 To UBound(myPort)
    18. CbxPort.Items.Add(myPort(i))
    19. Next
    20. CbxPort.Items.Add(noDevicetxt)
    21. CbxPort.Text = CType(CbxPort.Items.Item(0), String)
    22. CbxBaud.Text = CType(CbxBaud.Items.Item(0), String)
    23. BtnDisconnect.Enabled = False
    24. End Sub
    25. Private Sub BtnConnect_Click(sender As Object, e As EventArgs) Handles BtnConnect.Click
    26. If CbxPort.SelectedItem.ToString = noDevicetxt Then
    27. MessageBox.Show("Please connect Serial Device first! (Connect and restart Programm)")
    28. Exit Sub
    29. End If
    30. SerialPort1.PortName = CbxPort.Text
    31. SerialPort1.BaudRate = CInt(CbxBaud.Text)
    32. SerialPort1.Parity = IO.Ports.Parity.None
    33. SerialPort1.StopBits = IO.Ports.StopBits.One
    34. SerialPort1.DataBits = 8
    35. 'Micro Arduino EXTRA UNO ging ohne ''https://www.visualmicro.com/forums/YaBB.pl?num=1431524832 Post Nr.14
    36. SerialPort1.DtrEnable = True
    37. SerialPort1.RtsEnable = True
    38. 'Micro END
    39. SerialPort1.Encoding = System.Text.Encoding.Default
    40. SerialPort1.Open()
    41. SerialPort1.DiscardOutBuffer()
    42. BtnConnect.Enabled = False
    43. BtnDisconnect.Enabled = True
    44. End Sub
    45. Private Sub BtnDisconnect_Click(sender As Object, e As EventArgs) Handles BtnDisconnect.Click
    46. SerialPort1.Close()
    47. BtnConnect.Enabled = True
    48. BtnDisconnect.Enabled = False
    49. End Sub
    50. Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    51. Me.Invoke(Sub() ReceivedText(SerialPort1.ReadExisting()))
    52. End Sub
    53. Private Sub ReceivedText(ByVal txt As String)
    54. Me.RtxAusgabe.Text &= txt
    55. End Sub
    56. End Class


    Insbesondere frag ich mich ob das mit der Sub SerialPort // ReceivedText und dem Delegaten so okay ist leider verstehe ich das Thema nicht, obwohl ich schon einiges gelesen habe.

    *Topic verschoben*
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    nogood schrieb:

    Kann ich das so machen ?
    Einiges geht besser:

    VB.NET-Quellcode

    1. CbxPort.Items.AddRange(IO.Ports.SerialPort.GetPortNames())
    genügt.
    Wenn der Arduino nur eine BaudRate kann, kannst Du die fest vorgeben.
    Musst Du dem Arduino nicht auch Befehle senden? Da fehlt wohl noch was.
    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!
    Danke für das okay ;)

    Befehle senden hab ich mich noch nicht drum gekümmert. Ziel war es einen kompakten simplen Serial-Monitor zu coden der alles anzeigt, was am Serialport ankommt.

    -----
    Eine Sache hätte ich noch gerade

    VB.NET-Quellcode

    1. Private Sub Form1_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
    2. If SerialPort1.IsOpen Then SerialPort1.Close()
    3. End Sub


    Sollte ich das noch mit rein nehmen?
    codewars.com Rank: 4 kyu

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

    nogood schrieb:

    mit rein nehmen?
    ist OK.
    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!
    Nochmal langsam für ältere Herren zum mitdenken (wie im Title steht nullcheck von invoke und delegaten):

    VB.NET-Quellcode

    1. Delegate Sub SetTextCallback(ByVal txt As String)
    2. Private Sub ReceivedText(ByVal txt As String)
    3. Me.RtxAusgabe.Text &= txt
    4. End Sub


    1. maximal ausgeschriebener Code:

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. dim txt As String = SerialPort1.ReadExisting()
    3. Me.Invoke(new settextcallback (AddressOf ReceivedText), txt)
    4. End Sub


    2. Könnte man auch kürzer coden:

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Me.Invoke(new settextcallback (AddressOf ReceivedText), SerialPort1.ReadExisting())
    3. End Sub


    3. und nochmal kürzer:

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Me.Invoke(Sub() ReceivedText(SerialPort1.ReadExisting()))
    3. End Sub


    Frage A
    Also 1. 2. & 3. machen exakt das gleiche es ist nur effizienter geschrieben? j/n

    Frage B
    Wie weis der Kompiler das in 3. Me.Invoke(Sub() ... Sub() die delegaten Sub settextcallback ist (was ist wenn man mehrere Delegaten Subs hätte inkl. String Typ)?

    Danke für Feedback
    codewars.com Rank: 4 kyu
    2 und 3 machen nicht das Gleiche!

    Bei 2 wird folgendes gemacht, in dieser Reihenfolge:
    1. Die Adresse von ReceivedText wird "geholt/rausgesucht" (man sagt "ausgewertet").
    2. Eine Instanz des Delegattypen SetTextCallback wird mit dieser Adresse erstellt.
    3. SerialPort1.ReadExisting wird aufgerufen
    4. Me.Invoke wird aufgerufen, mit der Instanz des Delegattypen und dem Ergebnis von SerialPort1.ReadExisting als Argumente.
    5. (Im GUI-Thread) ReceivedText wird aufgerufen, mit dem Ergebnis von SerialPort1.ReadExisting als Argumente.

    Bei 3 wird folgendes gemacht, in dieser Reihenfolge:
    1. Die Adresse von einer anonymen Methode wird ausgewertet.
    2. Eine Instanz eines Delegattypen (nicht SetTextCallback in dem Fall, ist aber irrelevant) wird mit dieser Adresse erstellt.
    3. Me.Invoke wird aufgerufen, mit der Instanz des Delegattypen als Argument. (Beachte hier: Nur ein Argument.)
    4. (Im GUI-Thread) Die anonyme Methode wird aufgerufen. (Kein Argument.)
    5. SerialPort1.ReadExisting wird aufgerufen.
    6. ReceivedText wird aufgerufen, mit dem Ergebnis von SerialPort1.ReadExisting als Argument.

    Also ReceivedText wird in beiden Varianten im GUI-Thread aufgerufen.
    Aber bei 2 wird SerialPort1.ReadExisting im Hintergrundthread aufgerufen und bei 3 wird es im GUI-Thread aufgerufen. Welche Variante richtig ist, weiß ich nicht, aber das ist der Unterschied.

    Allgemein möchte ich aber das hier anbringen: Serial Port
    Die SerialPort-Klasse scheint nicht sehr gut gebaut zu sein und manchmal Daten zu verschlucken, wenn man das DataReceived und ReadExisting verwendet.

    Und zu Delegaten hier eine Erklärung: Frage zu Delegaten und Invokes
    Dazu möchte ich noch hinzufügen:
    Byte ist ein Datentyp, der vorzeichenlose 8-Bit-Ganzzahlen beschreibt.
    Int32 ("Integer") ist ein Datentyp, der vorzeichenbehaftete 32-Bit-Ganzzahlen beschreibt.
    DateTime ist ein Datentyp, der Zeitpunkte beschreibt. (Welches Jahr? Welches Monat? Welcher Tag? Welche Stunde? etc.)
    Form ist ein Datentyp, der ein Windows-Fenster beschreibt. (Welche Position? Welche Größe? Welcher Fenstertitel? etc.)
    Und nach dem gleichen Schema ist ein Delegat ein Datentyp, der eine Methode beschreibt. (Wie viele Parameter? Welche Parametertypen? Welcher Rückgabetyp? etc.)
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

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

    @Niko Ortner Puuh Danke für die ausführliche Antwort. Den Link der Probleme über das DataReceived Event beschreibt hab ich von Dir schon mal gefunden und angesurft, aber das hatte mich damals überfordert. Schau ich mir mit etwas mehr Wissen von heute nochmal an.

    Spontan würde ich jetzt die Variante 2. bevorzugen. Da hab ich das Gefühl das ich das eher verstehe.

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Me.Invoke(new settextcallback (AddressOf ReceivedText), SerialPort1.ReadExisting())
    3. End Sub



    In meinen Worten:
    Auf Thread A läuft Form1. Der User kann die ganze Zeit ununterbrochen auf der GUI rumklicken (es soll ja nichts haken daher darf der Thread nicht mit rechen/zeit-intensiven Sachen unterbrochen werden ). Im Fall, dass das Event SerialPort1.DataReceived in Thread A ausgelöst wird passiert folgendes:
    Form1 invokes (wecktauf/erzeugt) einen neuen Thread B in diesem Thread wir ein Objekt der DelegatenKlasse Sub SetTextCallBack erzeugt. Dieses Objekt ist ein "Zeiger" der im Speicher auf die Adresse zeigt an der die Sub ReceivedText ihren String ausließt und schreibt hier das rein, was am SerialPort ankommt und ruft dann die Sub ReceivecText auf, diese Sub zeigt dann im Textfeld in Form1 die Daten/String an. Das alles läuft auf Thread B.

    So wäre sichergestellt, dass falls das Auslesen am SerialPort z.B. eine Sekunde dauern würde der User in dieser Sekunde im GUI weiterarbeiten könnte ohne das er merkt das die CPU mit etwas anderem beschäftigt ist.

    Frage:
    Wie richtig / falsch ist meine Denkweise? Wenn ich deine Antwort durchlese sehe ich, dass ich anscheinend danebenliege. Ich dachte das ReceivedText in Thread B aufgerufen wird und nicht vom GUI!

    Nochmal -> die Delegaten Sub macht 2 Dinge sie zeigt, an welcher Stelle Daten im Speicher gespeichert werden sollen und ruft dazu noch die Sub auf die diese Daten dann weiterverarbeiten soll. Wäre die Beschreibung denn korrekt?
    codewars.com Rank: 4 kyu

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

    @nogood Deine Denkweise ist etwas merkwürdig, von "Zeiger im Speicher" spricht man in .NET nicht.
    Zunächst: Ich bevorzuge die dritte Code-Variante.
    Der Empfang selbst kann dauern so lange er halt dauert. Wenn Daten da sind, komt ein Event. Da gibt es keinen weiteren Zeitverlust.
    Wenn Du natürlich die Daten noch verarbeiten musst und das länger dauern sollte, kannst Du das in einem (weiteren) Thread tun.
    Dann solltest Du aber nicht zwischendurch in den GUI-Thread invoken, sondern gleich einen weiteren Thread starten.
    Wenn dieser dann mit seiner Arbeit fertig ist, invokt er in den GUI-Thread, denn nur im GUI-Thread können Daten zur Anzeige gebracht werden.
    Und mit Delegaten muss man heute nicht mehr arbeiten, und ohne sieht es einfach eleganter aus.
    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 Danke das Du dir das nochmal angeschaut hast.

    "... Deine Denkweise ist etwas merkwürdig..." Ich versuch ja das richtige zu denken :D Leider weiß ich es nicht besser.

    Zur dritten Code Variante -> dann ist aber auch schon in Post 1 die Zeile

    VB.NET-Quellcode

    1. Delegate Sub SetTextCallback(ByVal txt As String)


    hinfällig. Anscheinend hab ich die noch aus anderen Versuchen drin und ich habe nicht gemerkt das das weg kann.

    ----------------------------

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Me.Invoke(Sub() ReceivedText(SerialPort1.ReadExisting()))
    3. End Sub


    Die dritte Variante mal in Worte gefasst inklusive zeitlichem Aspekt und auf welchem Thread was läuft anhand der Ausführungen von Niko Ortners post:

    0. Form1 läuft auf Thread A. In genau diesem Thread wird ja auch der SerialPort geöffnet also schaut der Thread A auch, ob Daten am Serialport anliegen. Falls Daten am SerialPort anliegen wird das Event SerialPort1.DataReceived ausgelöst. ----> Wann wird das Event ausgelöst bei welcher Datenmenge ?

    Niko Ortners post:
    "1. Die Adresse von einer anonymen Methode wird ausgewertet." ----> anonyme Methode ist das so richtig? Der Typ (String, Int,...) der Methode ist unbekannt (da oben ja keine explizite Delegaten Sub mehr steht) aber der Name der Methode ist doch ReceivedText oder bezieht sich das alles auf sub()?

    "2. Eine Instanz eines Delegattypen (nicht SetTextCallback in dem Fall, ist aber irrelevant) wird mit dieser Adresse erstellt." ----> anstatt eine explizite Delegaten Sub zu benutzen wird durch Sub() angezeigt, dass das Prinzip Delegat benutzt werden soll?

    "3. Me.Invoke wird aufgerufen, mit der Instanz des Delegattypen als Argument. (Beachte hier: Nur ein Argument.)" ----> fängt jetzt Thread B an ?

    Wäre es möglich das Ihr nochmal den Weg beschreibt wie der Ablauf ist? Ich merk gerade ich kann es nicht!

    Niko Ortners Post beschreibt ja den Ablauf, aber ich versteh noch nicht wo da welcher Thread anfängt und was der jeweilige Thread für Dinge abarbeitet. Wäre es drin den Ablauf nochmal ausführlicher zu schreiben. Danke mir raucht etwas der Schädel... <X Sorry das es langsam immer wirrer bei mir wird
    codewars.com Rank: 4 kyu

    nogood schrieb:

    SerialPort
    Das SerialPort arbeitet in einem anderen Thread, insbesondere dann, wenn Du es im GUI-Thread öffnest.
    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 einiges bringst du noch durcheinander.

    Im Fall, dass das Event SerialPort1.DataReceived in Thread A ausgelöst wird passiert folgendes:
    Das DataReceived-Event wird eben nicht im Thread A (auch GUI-Thread) ausgelöst, sondern in einem anderen Thread (nennen wir ihn Thread B). Wenn du einen SerialPort öffnest (SerialPort.Open), dann wird ein weiterer Thread gestartet, der sich darum kümmert, die Kommunikation über den Port abzuarbeiten. Dieser Thread löst das DataReceived-Event aus. Die SerialPort_R-Methode läuft also in Thread B. Deshalb muss man ja überhaupt erst Invoke verwenden. Würde das Event im GUI-Thread ausgelöst werden, könnte man ohne Weiteres direkt auf GUI-Elemente zugreifen.
    Form1 invokes (wecktauf/erzeugt) einen neuen Thread B
    Invoke startet keinen neuen Thread. Me.Invoke fügt der Nachrichtenwarteschlange der Application, zu der Me (eine Form) gehört, eine Nachricht hinzu, die, wenn sie abgearbeitet wird, den angegebenen Delegaten aufruft.
    Ok, das ist viel auf einmal. Die Kurzfassung ist: Der GUI-Thread führt, sobald er kann, die Methode aus, die du bei Me.Invoke als Argument angegeben hast.
    Die Langfassung musst du nicht unbedingt lesen, aber wenn du willst, habe ich sie hier:
    Langfassung

    Jede ausführbare Datei (exe) fängt mit einem einzigen Thread an einer einzigen Stelle an. Das ist der sogenannte Einstiegspunkt, oder auch Main-Methode genannt. Die ist bei einfachen Anwendungen versteckt, man kann die aber selbst hinschreiben, wenn man in den Projekteinstellungen das Anwendungsframework deaktiviert:

    VB.NET-Quellcode

    1. Public Class Program
    2. Public Shared Sub Main(Args As String())
    3. Application.Run(New Form1)
    4. End Sub
    5. End Class

    Die Instanz von Form1, die da erstellt wird, ist einfach das erste Fenster, das geöffnet wird.
    Application.Run macht im Grunde genommen was ganz Einfaches:

    VB.NET-Quellcode

    1. Public Shared Sub Run(MainForm As Form)
    2. Do
    3. Dim Message = GetMessage()
    4. DispatchMessage(Message)
    5. Loop
    6. End Sub

    Ist natürlich stark vereinfacht dargestellt. So Sachen wie "Wie wird die Anwendung beendet, wenn man das Fenster schließt?" oder "Wie wird das erste Fenster überhaupt angezeigt?" ignorieren wir mal.
    GetMessage und DispatchMessage sind Win32-Methoden, die Windows bereitstellt.
    GetMessage holt sich die nächste Nachricht aus der Warteschlange (oder wartet bis eine da ist, wenn gerade keine da ist). DispatchMessage arbeitet die Nachricht ab. Super simpel.
    Beispiele für Nachrichten:
    Die Maus wurde an Position X Y über der TextBox 92377415 bewegt.
    Die linke Maustaste wurde über TextBox 92377415 gedrückt.
    Form 48224141 hat den Fokus verloren.
    TextBox 92377415 hat den Fokus erhalten.
    Die H-Taste wurde gedrückt, während TextBox 92377415 den Fokus hat.
    Wenn du z.B. einen EventHandler für das TextChanged-Event einer TextBox (der ominösen TextBox 92377415) hast, dann:
    • nimmt der GUI-Thread beim fünften Beispiel die Nachricht aus der Warteschlange,
    • schaut sich an, was es für eine Nachricht ist ("kann ich so weiterleiten"),
    • kramt die Instanz 92377415 raus (diese Zahl wird "Handle" genannt),
    • ruft an dieser Instanz die WndProc-Methode auf,
    • schaut sich an, was genau es für einen Nachricht ist ("Taste wurde gedrückt"),
    • fügt den Buchstabe "H" an den Text der TextBox an,
    • löst das TextChanged-Event aus und
    • führt deinen EventHandler aus.
    In deinem EventHandler könntest du z.B. den Sinn des Lebens berechnen, was eine Weile dauert. In dieser Zeit ist der GUI-Thread damit beschäftigt, deinen Code auszuführen und kann keine weiteren Nachrichten abarbeiten. Da gibt es jetzt zwei Dinge:
    1. sollte man solche langen Vorgänge in einen anderen Thread (wir nennen ihn Thread B) auslagern. Der GUI-Thread berechnet dann nicht den Sinn des Lebens sondern startet Thread B, und das Starten dauert nicht lange. Der GUI-Thread kann also gleich mit anderen Nachrichten weitermachen.
    2. wenn Thread B irgendwas an der GUI verändern will, kann er das nicht einfach so machen. Stell dir vor, der GUI-Thread ist gerade dabei, die 10 Items einer ListBox auf den Bildschirm zu zeichnen und Thread B kommt her und löscht den kompletten Inhalt der ListBox, während der GUI-Thread noch zeichnet. Da kann keiner sagen, was für Blödsinn rauskommen würde. Stattdessen fügt Thread B eine Nachricht der Art Der Delegat Foo soll mit Argumenten Bar aufgerufen werden. zur Warteschlange hinzu (das ist so gemacht, dass nichts kaputt geht, wenn mehr als ein Thread an der Warteschlange herumwerkeln will). Sobald der GUI-Thread damit fertig ist, die ListBox-Items zu zeichnen, macht er mit der nächsten Nachricht weiter. Irgendwann findet er die Nachricht, die Thread B reingelegt hat, schaut sich die an und ruft dann die Methode auf, auf die die Instanz des Delegaten zeigt und gibt Argumente mit, falls welche mitzugeben sind. Dadurch, dass der GUI-Thread diese Methode, die irgendwas an der GUI ändern will, selbst ausführt, kann er sich selbst nicht in die Quere kommen.
    Jetzt gibt es noch ein Detail: Wenn du im Thread B Me.Invoke aufrufst, wartet Thread B, bis die Nachricht vom GUI-Thread abgearbeitet wurde. Wenn du im Thread B Me.BeginInvoke aufrufst, wartet Thread B nicht, sondern schmeißt die Nachricht einfach in die Warteschlange und macht sofort weiter. Diese Unterscheidung kann manchmal sinnvoll sein.


    in diesem Thread wir ein Objekt der DelegatenKlasse Sub SetTextCallBack erzeugt.
    Die Instanziierung des Delegat-Typs passiert noch bevor Invoke aufgerufen wird. Ebenso SerialPort1.ReadText. Erst sobald die Instanz da ist und das Ergebnis von SerialPort1.ReadText da ist, wird Invoke mit den beiden als Parametern aufgerufen. Das passiert alles noch im gleichen Thread (also das, was du dachtest, was der GUI-Thread wäre, was tatsächlich aber Thread B ist).

    Dieses Objekt ist ein "Zeiger" der im Speicher auf die Adresse zeigt an der die Sub ReceivedText
    Hmmm. Es kann sein, dass du das Richtige meinst, aber um Missverständnisse zu vermeiden schreibe ich den Satz mal um: "Dieses Objekt ist ein "Zeiger" der auf den Speicher zeigt, wo die Sub ReceivedText liegt."
    ihren String ausließt
    Was ist ihr String und wovon wird er "ausgelesen"? Meinst du txt in Private Sub ReceivedText(ByVal txt As String)? Das ist ein Parameter. "Auslesen" kann man das nicht wirklich nennen. Der String wird der Methode beim Aufruf mitgegeben. Der String wird zum Argument für den txt-Parameter.
    und schreibt hier das rein, was am SerialPort ankommt
    Auch hier kann es sein, dass du das Richtige meinst, es nur ungewöhnlich ausgedrückt hast. Das, was am SerialPort ankommt (das Ergebnis von SerialPort1.ReadText) wird als Argument an die Invoke-Methode gegeben. Im GUI-Thread dann wird das, was am SerialPort ankommt (bzw. angekommen ist) als Argument für den txt-Parameter an die ReceivedText-Methode gegeben.
    Sub ReceivecText auf, diese Sub zeigt dann im Textfeld in Form1 die Daten/String an.
    Richtig.
    Das alles läuft auf Thread B.
    Das erstellen der Instanz des Delegat-Typen, das Aufrufen von SerialPort1.ReadText und das Aufrufen von Invoke passiert in Thread B. Das Aufrufen von ReceivedText und das Anzeigen des Textes im Textfeld passiert im GUI-Thread.

    die Delegaten Sub macht 2 Dinge sie zeigt, an welcher Stelle Daten im Speicher gespeichert werden sollen und ruft dazu noch die Sub auf die diese Daten dann weiterverarbeiten soll
    Der Delegat zeigt nur auf die Methode. Das Mitgeben von Daten, die dann von der Methode weiterverarbeitet werden sollen, wird von Invoke und den Nachrichten erledigt. Da haben Delegaten nicht wirklich was mit zu tun.

    @RodFromGermany an dieser Stelle:
    von "Zeiger im Speicher" spricht man in .NET nicht.
    Das stimmt schon, aber es kann für das Verständnis hilfreich sein, wenn man sich abstrakte Konzepte ein bisschen bildlicher vorstellen kann. Ist wie beim Erklären des Unterschieds zwischen Referenz- und Wertetypen. "Eine Variable eines Wertetyps beinhaltet einen Zeiger auf ein Objekt, das woanders im RAM liegt. Kopierst du den Inhalt der Variable, wird der Zeiger kopiert und das Objekt bleibt unverändert. Eine Variable eines Wertetyps beinhaltet das Objekt selbst. Kopierst du den Inhalt der Variable, kopierst du das ganze Objekt. Änderungen an einer Kopie verändern dann die andere Kopie nicht."


    dann ist aber auch schon in Post 1 die Zeile [...] hinfällig
    Ja, kann man weglassen. Händisch deklarierte Delegat-Typen sind nur sehr selten nötig, weil man in fast allen Situationen die generischen Delegat-Typen Action und Func verwenden kann. Wenn damit die Namen zu komplex werden würden, kann man eigens deklarierte Delegat-Typen als eine "Abkürzung" verwenden.


    Form1 läuft auf Thread A. In genau diesem Thread wird ja auch der SerialPort geöffnet also schaut der Thread A auch, ob Daten am Serialport anliegen.
    Eben wird ein anderer Thread (Thread B) gestartet, der schaut, ob Daten am SerialPort anliegen.

    Wann wird das Event ausgelöst bei welcher Datenmenge ?
    Das hängt von den Einstellungen am SerialPort ab. Ich kann dir aber nur sagen, dass das alles ziemlich schwammig ist, weil nicht gut (oder vielleicht auch nur sehr versteckt) dokumentiert ist, wie dieses Verhalten gesteuert wird. Besonders da ich nicht die Read- und Write-Methoden der SerialPort-Klasse verwende sondern direkt den BaseStream verwende (wie im verlinkten Post erklärt, und da kommen Daten ohne merkbare Verzögerung im Programm an, aber ich habe noch nie getestet, wie oft die Callback-Methode eigentlich aufgerufen wird und wie viel/wenig Bytes auf einmal gelesen werden), und auf MSDN steht, dass z.B. ReadTimeout und ReadBufferSize die BeginRead-Methode des BaseStreams nicht beeinflussen, kann ich dir nicht sagen, worauf du da achten musst.

    Der Typ (String, Int,...) der Methode ist unbekannt
    Mit "Typ der Methode" ist in dem Zusammenhang nicht ganz klar, was gemeint ist. Darauf komme ich gleich zurück.
    aber der Name der Methode ist doch ReceivedText oder bezieht sich das alles auf sub()?
    "Anonym" bezieht sich auf das Sub(), bzw. gibt es das auch mehrzeilig:

    VB.NET-Quellcode

    1. Me.Invoke(Sub()
    2. 'Hier Code
    3. End Sub)

    Was du mit "Typ der Methode" meinst, ist wahrscheinlich die sogenannte Signatur. Jede Methode hat eine Signatur, egal ob anonym oder nicht.
    Wenn du Sub ReceivedText(ByVal txt As String) hinschreibst, deklarierst du eine Methode, die einen Parameter vom typ String hat und nichts zurückgibt und sie hat den Name "ReceivedText", ist also nicht anonym.
    Wenn du Sub(ByVal txt As String) hinschreibst, deklarierst du eine Methode, die einen Parameter vom typ String hat und nichts zurückgibt und sie hat keinen Namen, ist also anonym.
    Aber beide haben die gleiche Signatur: Nimmt einen Parameter vom Typ String entregen und gibt nichts zurück.
    Nach dem Schema deklariert Sub() eine anonyme Methode, die keine Parameter hat und nichts zurückgibt.
    Ein (nicht-abstrakter) Delegat-Typ hat ebenfalls eine Signatur. Und diese Signatur muss mit der Signatur der Methode übereinstimmen, auf die die Instanz des Delegat-Typs zeigt. Also ein Delegate Sub Foo(Bar As String) kann nur auf Methoden zeigen, die einen Parameter vom Typ String haben und nichts zurückgeben.

    wird durch Sub() angezeigt, dass das Prinzip Delegat benutzt werden soll?
    Wie gesagt deklariert das Sub() hier eine anonyme Methode. Der Unterschied zwischen Me.Invoke(New SetTextCallback(AddressOf ReceivedText), ...) und Me.Invoke(AddressOf ReceivedText, ...) ist nur der, dass man im ersten Fall händisch sagt, welcher Delegat-Typ instanziiert werden soll, und es im zweiten Fall der Compiler selber rausfinden muss. Wenn die Invoke-Methode einen nicht-abstrakten Delegat-Typ entgegennehmen würde, würde der Compiler einfach den nehmen. Aber die Invoke-Methode nimmt den abstrakten Delegat-Typ namens Delegate entgegen. (Nicht verwechseln mit dem Schlüsselwort "Delegate", sondern der vollständige Name dieses Typs lautet "System.Delegate".) Der Compiler kann den nicht einfach verwenden, weil dann müsste er eine Instanz davon erstellen, was ja nicht geht, weil er abstrakt ist. Also meckert er. Im Fall von Me.Invoke(Sub() ..., ...) deklariert der Compiler im Hintergrund einen Delegat-Typ dessen Signatur die gleiche wie die der anonymen Methode ist und erstellt davon eine Instanz. Das würde auch für den zweiten Fall oben funktionieren, aber keine Ahnung, wieso man sich in dem Fall dagegen entschieden hat.

    fängt jetzt Thread B an ?
    Ja, aber halt eben nicht Thread B sondern der GUI-Thread, bzw. nicht sofort, sondern sobald der GUI-Thread die Nachricht abarbeitet. Und Thread B wartet an der Stelle, bis der GUI-Thread die Methode, auf die der Delegat zeigt, fertig abgearbeitet hat. (Siehe dazu die Langfassung oben.)


    Übrigens:
    Ich persönlich bin für Variante 4:

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Dim Data = SerialPort1.ReadExisting()
    3. Me.Invoke(Sub() ReceivedText(Data))
    4. End Sub

    Hat den Vorteil, dass du in die anonyme Methode beliebigen Code schreiben kannst, auch wenn mehr passieren soll, und dafür nicht extra nochmal eine nicht-anonyme anlegen musst. Ein komplexeres Beispiel:

    VB.NET-Quellcode

    1. Private Sub SerialPort_R(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Dim RawData = SerialPort1.ReadExisting()
    3. Dim SanetizedData = SanetizeSerialData(RawData)
    4. Me.Invoke(Sub()
    5. ReceivedText(SanetizedData)
    6. UpdateStuff()
    7. QueueReparse()
    8. End Sub)
    9. End Sub



    Noch eine Anmerkung:
    Ich weiß nicht, ob SerialPort1.ReadExisting in Thread B aufgerufen werden muss. Es erscheint mir sinnvoll, dass das so ist, aber es kann auch sein, dass man sie auch in einem anderen Thread aufrufen darf. Die Dokumentation sagt dazu jedenfalls nichts.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @Niko Ortner Ich habe gerade erst gesehen, dass Du geantwortet hast. Im Moment fällt mir nur ein, mich bei Dir zu bedanken. Ich hab die Post erst einmal gelesen. Das reicht bei mir nicht, um es zu verstehen. Ich hoffe, dass ich das morgen mit frischem Geist besser hin bekomme. Danke für die Mühe
    codewars.com Rank: 4 kyu