Synchronisieren des Zugriffs auf Subs und Funktionen (Thread und Userzugriff)

  • VB.NET

Es gibt 34 Antworten in diesem Thema. Der letzte Beitrag () ist von metzelmax.

    Synchronisieren des Zugriffs auf Subs und Funktionen (Thread und Userzugriff)

    Hallo allerseits,

    ich grüble nun schon geraume Zeit an einer
    Variante, wie ich Geräte permanent über die serielle Schnittstelle
    abfragen kann. Gelöst habe ich dies bisher mittels eines eigenen Threads
    in der Geräteklasse welcher zwischen 2 und 5 Subs bzw. Funktionen
    permanent nacheinander aufruft und bei sich ändernden Werten dies
    mittels Event mitteilt. Die serielle Schnittstelle ist dabei via
    Synclock entsprechend gesperrt.

    Beispiel:

    Die Funktion ReadValue liest einen Wert vom Gerät. Die Funktion kann sowohl vom User
    als auch von einem Thread ausgeführt werden. Wenn nun jedoch der User
    ReadValue aufruft und gleichzeitig der Thread diese Funktion bearbeitet,
    könnte es vorkommen das Variablen innerhalb der Funktion einen
    undefinierten Zustand erhalten.
    Vereinfacht und ohne Fehler abzufangen zB. so:

    Function ReadValue() as Double
    Dim Antwort as String = Sende(GibWert)
    Dim Value as Double = -9999
    If Antwort <> nothing then Value= cdbl(Antwort)
    Return Antwort
    end function

    Function Sende as String
    Synclock Comport
    With comport
    .write(Befehl)
    Dim Antwort as string = .readto(crhw(13))
    Return Antwort
    end synclock
    End function

    Gibt es eine Möglichkeit den Zugriff auf Variablen oder Funktionen/subs zu
    Synchronisieren? Oder kennt jemand vielleicht andere Möglichkeiten um
    ein solches Problem zu lösen?

    metzelmax schrieb:

    wie ich Geräte permanent über die serielle Schnittstelle abfragen kann.
    Ist das die geeignete Herangehensweise?
    Wann / unter welchen Bedingungen sendet Dein Gerät?
    Der Empfang einer Sendung vom Gerät führt zu einem Interupt in xdeinem Programm, so dass kein Polling erforderlich 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!
    Hallo und danke für die Antworten bis jetzt :).

    Ich versuche das ganze mal etwas zu konkretisieren:
    Das Programm ließt mehrere Messwerte von verschiedenen seriellen Geräten aus. Dazu muss man an das Gerät einen Befehl senden mit dem man angibt was man haben möchte (z.B. "GibTemperatur"). Das Gerät sendet dann den gewünschten Wert (z.B. "Temp 30,5"). Damit ich nicht über einen Timer jede Sekunde z.B. einen Temperaturwert abfragen muss, habe ich das in der jeweiligen Geräteklasse durch einen Thread gelöst, welcher permanent verschiedene Werte über Subs/Funktionen abfragt und bei Änderungen ein entsprechendes Event wirft. Im eigentlichen Programm nutze ich dieses Event dann um z.B. den Wert in einem Label anzeigen zu lassen.
    Nun möchte man die Daten aber auch gern Speichern. Ich nutze dafür eine Datatable in welche ich die Werte einschreibe. Dazu nutze ich einen Timer in welchem ich dann die gewünschten Werte abfrage. Diese Abfrage greift dann jedoch auf einige Funktionen/Subs wie der Thread zu, wodurch es meiner Meinung nach zu Komplikationen kommen kann.

    Das Synclock selbst blockt nur den Port solange wie das Senden/Empfangen dauert. Eventuelle Aufbereitungen der Antwort (Konvertierung zu einem Double oder entfernen unnötiger zeichen) sind nicht gelockt.

    metzelmax schrieb:

    Eventuelle Aufbereitungen der Antwort sind nicht gelockt.

    Und? Das ist doch lokal in deiner Sub/Function. Wenn das auch gelockt werden soll, zieh einfach den SyncLock-Block über alles rüber. Achtung! Das ist nicht die feine englische Art!
    Mit freundlichen Grüßen,
    Thunderbolt
    Wieviel verschiedene Geräte hast Du abzufragen?
    Ist jedes davon an einer separaten Schnittstelle?
    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!
    Synclock ist auch das falsche Mittel. Controls muss man mit Control.BeginInvoke synchronisieren.
    Ich finds auch irre, für jeden Serialport zum Pollen einen eigenen Thread im Dauerbetrieb zu haben.
    Wenn Pollen im NebenThread, dann mit System.Threading.Timer.
    Und da reicht einer.
    Also es handelt sich um mehrere Geräte. Drei besitzen eine Separate RS232-Schnittstelle, vier laufen über einen RS485 Bus welcher über eine RS232-Schnittstelle angesprochen wird.

    Die Kommunikation läuft an sich problemlos und es kommt nahezu nie zu fehlern (Hauptsächlich Timeouts). Allerdings passiert es manchmal, dass das Programm hängt und erst wieder reagiert wenn man mit der rechten Maustaste auf die Taskleiste klickt. Komisches Verhalten, was ich mir bisher nicht erklären konnte und vor allem nervig ist wenn es ne Stunde lang nicht bemerkt wird...

    In Controls schreibe ich mittels Invoke. Den Seriellen Port sperre ich nur für die Zeit des Sendens/Empfangens. Insbesondere Bei RS485 Bus käme es sonst zu Deadlocks weil ja jedes Gerät mit seinem Thread möglichst gleichzeitig auf die Schnittstelle zugreifen wollen. Ich Polle die Geräte damit ich immer den aktuellsten Wert in meinen Controls anzeigen kann. Funktioniert prima solang das Prog nicht grad hängt;). Jedes Gerät hat dabei genau einen Thread welcher dabei die gewünschten Infos permanent abruft.

    Der System.Threading.Timer ist vergleichbar mit einem normalen Timer? Hab den noch nicht verwendet... Bin noch nicht so fit was Multithreading angeht...:(


    Noch eine Frage zur parallelen Ausführung der Aufbereitungsroutine:

    Wenn ich eine Funktion habe, welche mein Ergebnis aufbereitet z.B.

    Function Aufbereitung (Byval Text as string) as String
    Text = Text.Remove(chw(13))
    Text = Text. Substring (2,5)
    Return Text
    end function

    Wenn nun ein Text durch Useraufruf übergeben wird und kurze Zeit später der Thread die gleiche Funktion aber mit einem anderen Text anspringt... Dann müsste doch der Usertext durch den Threadtext ersetzt werden und der Rückgabewert undefiniert sein oder hab ich da meinen Denkfehler.
    das mittm seriellen Port und Busse usw versteh ich nicht. Ich meine mit SerialPort eine Klasse im Framework. Ich weiß nichtmal, wie man da Geräte dranhängt, und ob man ühaupt mehrere an einen Serialport hängen kann - täte mich wundern.

    Also naiv täte ich denken: pro Gerät einen Serialport instanzieren, und dann hat man Methoden wie Read und Write, und v.a. ein DataReceived-Event.

    Jut, und wenndenun pollen willst, mittm Threading-Timer. Ist prinzipiell wie ein WinForms-Timer, also das ding tickt. Nur das Event (heißt glaub anners als "tick") läuft bereits im NebenThread, mit allen Problemen und Möglichkeiten, die das bietet.
    Gucks dir im ObjectBrowser an.

    ErfinderDesRades schrieb:

    und ob man ühaupt mehrere an einen Serialport hängen kann
    Kein Problem, da heißt das ganze CAN-Bus (das wäre das Protokoll).
    Alle Geräte an diesem Bus sind für den Rechner / das Programm quasi ein Gerät.
    @metzelmax:: Ich hab immer noch keinen rechten Plan, wie Dein Programm abläuft.
    Mein Gefühl sagt mir, dass Du iwie Daten und GUI vermatschst, wenn dreist auch mit Hilfe von Invoke.
    Also.
    Wir haben einen Timer, und im richtigen Takt werden die Geräte abgefragt.
    Jedes Ports hat sein DataReceived-Event.
    Da musst Du nix synchronisieren, solange die Daten mehrerer Ports nicht in einem Topf landen (Control oder Array).
    Kannst Du mal bitte etwas mehr auf die Interna eingehen?
    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:

    Jedes Ports hat sein DataReceived-Event.
    Reden wir jetzt von meiner Klasse "SerialPort"?
    Dann kannich wieder mitreden, nämlich, dass dieses Event im NebenThread einläuft, und Aktionen ins Gui daher mawieder mit Control.BeginInvoke zu transferieren sind.

    ErfinderDesRades schrieb:

    meiner Klasse "SerialPort"?
    System.IO.Ports.SerialPort
    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!
    Sorry wenn ich mich unklar ausdrücke. Also es handelt sich natürlich um Geräte welche überSystem.Io.Ports.Serialport angesprochen werden. Der Bus unterscheidet sich da nur dahingehend das, wie Rod richtig bemerkt, alle Bus-Geräte über den selben Port angesprochen werden.
    Allerdings nutze ich bisher nicht das Datarecive-Event des jeweiligen Ports. Den Empfang realisiere ich, indem ich nach dem senden die Antwort in einer Do Loop-Schleife Zeichen für Zeichen einlese und diese bei Erhalt des Ende-Zeichens (z.B. CR) abbreche. Das Empfangene Datenpaket gebe ich dann als Return an die aufrufende Sub/Funktion zurück. Kommt es zu einem Fehler oder Timeout gibt es entsprechend einen Fehlerwert. Der port wird für diese Zeit per Synclock gesperrt. Bei Nutzung des Datarecive-Events müsste ich immer erst schauen wer zuletzt eine Anfrage über den Port geschickt hatte um die Antwort entsprechend richtig zuordnen zu können. Wusste nicht wie ich das effektiv lösen kann und hab mich daher für die oben beschriebene Lösung entschieden.

    So nun nochmal der Programmablauf:
    Nach dem Instantiieren aller Geräte und dem Start einer Messung werden die entsprechenden Label auf der GUI durch die Events der Geräteklasse mit den Werten gefüllt und aktualisiert (per Invoke da die Events ja auch durch den Thread erzeugt sein könnten). Ein Timer läuft zusätzlich um diese und weitere Daten der Geräte abzufragen und diese in eine Datarow einer Datatable einzutragen. Dies geschieht bisher noch durch den "Hauptthread" da ich keinen eigenen Timer-Thread verwende. Fehlermeldungen beschränken sich hierbei im wesentlichen auf vereinzelte, seltene Timeouts. Aber ab und zu kommt es auch zum hängen des Programms. Daher frage ich mich ob das durch den Gleichzeitigen zugriff von Threads auf Variablen kommen kann welche dann undefinierte Zustände verursachen. Ich bring nachher noch ein Beispiel, muss jetzt aber mal kurz weg...

    metzelmax schrieb:

    alle Bus-Geräte über den selben Port angesprochen werden.
    jo, das ist sicher schwierig. Da müsste sich ja jedes Gerät iwie "ausweisen", weil du kannst dich glaub nicht drauf verlassen, dass die Geräte in genau der Reihenfolge antworten, wie du sie anfragst.

    Spricht iwas dagegen, jedem Gerät seinen eigenen SerialPort zu spendieren?

    ErfinderDesRades schrieb:

    Da müsste sich ja jedes Gerät iwie "ausweisen"
    Das ist kein Problem, wenn das ganze wie beim CAN-Bus funktioniert. In der Antwort ist die Geräte-Nummer enthalten (0 ... 255).
    @metzelmax:: Das Problem scheint mir in der Tat das explizite Pollen zu sein.
    Kann es sein, dass Du alle Geräte in einem Spagetti-Monster-Code abarbeitest?
    Probier mal einfach aus, indem Du eine allgemeine Klasse für Deine Geräte schreibst, die ein SerialPort als Member hat.
    Jede dieser Instanzen hält genau ein an ein Port angeschlossenes Gerät bzw. Gerätegruppe.
    Nutze das Received-Event und Du wirst wieder froh.
    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!
    Nee, ist kein Monstercode. Ich habe eine Basisklasse in der ich die Verwaltung des Ports sowie alle Funktionen des Threads beherberge. Da ist auch eine Send-Function welche als Parameter den Port und den jeweiligen Befehl entgegen nimmt. Leider kommunizieren drei der Geräte im Bus via Ascii, eines jedoch über Modbus (also Bytes). Ein Recive-Event zu nutzen ist dadurch zusätzlich erschwert.
    Die Basisklasse erbt jedes Gerät und nutzt dabei den Ports und die Funktionen. Eine spezielle Methode wird ebenfalls vererbt, in welche ich alle Subs/Funktionen packen kann welche der Polling-Thread abrufen soll.

    VB.NET-Quellcode

    1. Friend Function Send(ByRef Port As SerialPort, ByVal Befehl As String, Optional ByVal Nur_Senden As Boolean = False, Optional ByVal Send_Termini As String = vbCr, Optional ByVal Empf_Termini As String = vbCr) As String
    2. SyncLock Port
    3. With Port
    4. Try
    5. .DiscardInBuffer() 'Falls ein thread vorzeitig abgebrochen wurde und noch daten
    6. 'im puffer stehen werden diese zuvor gelöscht um bei der Auswertung später keine fehler zu erhalten!
    7. WaitTime(TimeBeforeSend) '5ms vor befehlsabgabe warten um Leitung "Frei" zu machen
    8. .Write(Befehl & Send_Termini)
    9. If Nur_Senden Then Return "OK"
    10. Dim Antwort As String = Nothing
    11. Dim sw As New Stopwatch
    12. sw.Start()
    13. Do
    14. Antwort &= .ReadExisting 'Zeichen für zeichen einlesen
    15. If sw.ElapsedMilliseconds >= Port.ReadTimeout Then Throw New TimeoutException()
    16. Loop Until Antwort.Contains(Empf_Termini) 'Wenn CR empfangen wurde kann schleife verlassen werden
    17. If Antwort <> Nothing Then
    18. Return Antwort
    19. Else
    20. Return FehlerString
    21. End If
    22. Catch ex As TimeoutException
    23. OnErrorOccurs(Fehlercodes.Timeout, ex.Message)
    24. Return FehlerString
    25. Catch ex As Exception
    26. OnErrorOccurs(Fehlercodes.Unbekannter_Fehler, ex.Message)
    27. Return FehlerString
    28. End Try
    29. End With
    30. End SyncLock
    31. End Function
    32. 'und in der Geräteklasse steht dann z.B. eine solche Funktion:
    33. Class Beispiel
    34. Private Function SendeBefehl(ByVal Befehl As String) As String
    35. Dim Antwort As String = Send(Comport_RS485, Befehl)
    36. Antwort = Antwort.Trim(Chr(10), Chr(13), " "c)
    37. If Antwort.Contains(">") OrElse Antwort.Contains("!") Then 'Antwort scheint OK
    38. Return Antwort
    39. Else
    40. Return FehlerString
    41. End If
    42. End Function
    43. 'und eine Beispielfunktion für ein Gerät zum auslesen verschiedener Werte
    44. Public Sub Read_AllChannels()
    45. Dim Antwort As String = SendeBefehl("#" & Busadresse)
    46. If Antwort <> FehlerString Then
    47. Dim Werte() As Double = AufbereitungMultiValue(Antwort)
    48. For i As Integer = 0 To Werte.Length - 1
    49. Kanal(i).LastValue = Werte(i)
    50. Next i
    51. End If
    52. End Sub
    53. End Class


    Die Frage ist nun ob die Funktion Read_AllChannels, welche sowohl vom User als auch vom Thread aufgerufen werden kann, hier Probleme machen kann. Im Prinzip ist ja nur die Send-Funktion der Basisklasse via Synclock geschützt, die Variable Antwort aber nicht. Ebenso ungeschützt ist die Funktion AufbereitungMultiValue welche den Empfangenen String zerlegt und Konvertiert. Oder ist das gar kein Problem?
    Das ist iwie alles auf DOS 6.3-Basis.
    Die Rückgabe von Send() ist die Antwort.
    Wenn Du eine Pizza bestellst, rennst Du doch nicht alle 5 Minuten zur Tür, um nachzusehen, ob der Pizzabote da ist, Du wartest, bis er klingelt.
    Und wenn Du mit 9 Leuten eine Fete feierst und jeder iwo bei Pizza, Nudel, Chinese, Grieche, Mudder oder sonst wo sein Essen bestellt, ist es kein Problem, denn nach (maximal) 10 Mal Klingeln hat jeder sein Essen. Nur müssen die ersten dann eben auf die letzten warten.
    Also:
    Schmeiß Deinen Code weg und bau ihn ordentlich auf.
    Pro Gerät / Gerätegruppe eine Instanz einer Klasse, die ggf. noch von einer gemeinsamen Datenbasis erben kann.
    Jede Instanz sendet auf Anfrage und bekommt ihre Antwort im DataReceice-Event. Da kann sie die Daten aufgereiten.
    Jede Instanz raised ein Event an ihr Parent, wenn die Antwort vorliegt.
    Alle diese Instanzen sind in einer List(Of IWas) drin, diese meinetwegen wiederum in einer separaten Klasse.
    Immer wenn Antwort-Event ankommt, werden die Daten einsortiert und es wird überrprüft, ob die Antwort vollständig ist.
    Wenn ja: Event an die GUI, wenn nein: warten oder ein TimeOut (Timer) auslösen bzw. Fehlermeldung.
    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!