System.Threading.Timer mit Delegat in Klassenbibliothek verwenden

  • VB.NET

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    System.Threading.Timer mit Delegat in Klassenbibliothek verwenden

    Hallo,

    ich bin mir nicht sicher, ob die Überschrift 100%ig passend ist, aber derzeit fällt mir nichts besseres ein.

    Worum gehts:
    ich schreibe derzeit an einem Programm mit GUI + verschiedenen Klassenbibliotheken in unterschiedlichen Namespaces, teilweise VB, teilweise C#.
    Eine der Klassenbibliotheken kommuniziert über eine serielle Schnittstelle und fragt Timer-gesteuert ab, ob neue Daten am COM-Port anliegen.
    Wenn ja (und vollständig), dann werden die ausgewertet und es werden entsprechende Ereignisse abgefeuert. Hier ein wenig Code:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class SerialComm()
    2. Private readtimer As System.Threading.Timer
    3. Private invervalValue as Long
    4. Public Event DataReceived(byref sender as Object, byval data as String)
    5. Public Sub New(ByVal port As SerialPort, ByVal interval As Long)
    6. Dim tcb As System.Threading.TimerCallback = AddressOf read
    7. intervalValue = interval
    8. readtimer = New System.Threading.Timer(tcb, Nothing, Timeout.Infinite, intervalValue)
    9. End Sub
    10. Public Sub Open()
    11. readtimer.Change(0, intervalValue)
    12. End Sub
    13. Public Sub Close()
    14. readtimer.Change(Threading.Timeout.Infinite, intervalValue)
    15. End Sub
    16. Private Sub read(state As Object)
    17. ' [...] Daten von serieller Schnittstelle lesen...
    18. Dim data as String = "Daten von serieller Schnittstelle"
    19. RaiseEvent DataReceived(Me, data)
    20. End Sub
    21. End Class


    Derzeit ist es so, dass in der GUI mit Me.InvokeRequired() kontrolliert wird, ob zur Thread-sicherheit ein Delegat nötig ist oder nicht. Also z.B.:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class MyForm Inherits Form
    2. Private WithEvents ser As SerialComm
    3. Public lblOut as Label
    4. Protected Delegate Sub dSetLabelText(byval text as string)
    5. Protected Sub RealSetLabelText(byval text as string)
    6. lblOut.text = data
    7. End Sub
    8. Protected Sub SetLabelText(byval text as String)
    9. If (Me.lblOut.InvokeRequired) Then
    10. Dim d As New dSetLabelText(AddressOf RealSetLabelText)
    11. Me.Invoke(d, New Object() {text})
    12. Else
    13. Me.RealSetLabelText(text)
    14. End If
    15. End Sub
    16. Protected Sub ser_DataReceived(byref sender as Object, byval data as String) Handles ser.DataReceived
    17. SetLabeltext(data)
    18. End Sub
    19. End Class



    Falls ich mich nicht vertippt habe, dann sollte das so in etwa auch funktioniere, ich habe hier nicht den vollständigen Original-Quellcode gepostet sondern nur einen Zusammenschnitt...

    Jetzt handelt es sich aber in Realität nicht nur um 1 Event, sondern um deutlich mehr, und ich möchte daher die komplette Geschichte mit den Delegaten usw. direkt in die SerialComm-Klasse verschieben.
    Dies ist so in etwa mein Versuch, aber klappt nicht:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class SerialComm()
    2. Private readtimer As System.Threading.Timer
    3. Private invervalValue as Long
    4. Public Event DataReceived(byref sender as Object, byval data as String)
    5. Private Delegate Sub dOnDataReceived(byref sender as Object, byval data as String)
    6. Private Sub realOnDataReceived(byref sender as Object, byval data as String)
    7. raiseevent DataReceived(me, data)
    8. End Sub
    9. Private Sub onDataReceived(byref sender as Object, byval data as String)
    10. Dim d As New dOnSerialReceived(AddressOf realOnDataReceived)
    11. d.Invoke(sender, data)
    12. End Sub
    13. Public Sub New(ByVal port As SerialPort, ByVal interval As Long)
    14. Dim tcb As System.Threading.TimerCallback = AddressOf read
    15. intervalValue = interval
    16. readtimer = New System.Threading.Timer(tcb, Nothing, Timeout.Infinite, intervalValue)
    17. End Sub
    18. Public Sub Open()
    19. readtimer.Change(0, intervalValue)
    20. End Sub
    21. Public Sub Close()
    22. readtimer.Change(Threading.Timeout.Infinite, intervalValue)
    23. End Sub
    24. Private Sub read(state As Object)
    25. ' [...] Daten von serieller Schnittstelle lesen...
    26. Dim data as String = "Daten von serieller Schnittstelle"
    27. RaiseEvent DataReceived(Me, data)
    28. End Sub
    29. End Class



    irgendwelche Ideen, was ich hier falsch mache???

    Danke,

    Christoph

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „c.muehlmann“ ()

    @c.muehlmann: Willkommen im Forum. :thumbup:
    Hast Du Dir mal die Klasse SerialPort angesehen?
    Son Ding kannst Du im Designer auf Deine Form ziehen und ganz easy benutzen.
    Benuzte dazu bitte die Suchfunktion, da gibt es mindestens 17 Threads, die Dein Problem exakt lösen.
    Soll heißen, dass eine Ausrede Deinerseits: "Ich hab die Suchfunktion benutzt aber nix passendes gefunden" nicht akzeptiert wird.
    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!
    Hi,

    Danke für Eure Antworten.

    @big-d:
    ja, der Timer verwendet als Callback die Methode read(). Dort werden die Zeichen der seriellen Schnittstelle eingelesen, auf Vollständigkeit prüft und ausgewertet.
    Das funktioniert aber auch, weshalb ich das im gegebenen Code weggelassen habe bzw. der Einfachheit halber dort durch den String data ersetzt habe.
    In der Methode read() wird dann, je nachdem was bisher auf der seriellen Schnittstelle angekommen ist, ein bestimmtes Ereignis ausgelöst, in meinem Beispiel der Event DataReceived(), es gibt hier aber noch diverse andere Events, die möglich sind (je nachdem, was am Seriellen Port angekommen ist).
    Bisher wurden diese Ereignisse in der GUI ausgewertet, d.h. dort wurde über InvokeRequired() geprüft, ob das Ereignis thread-übergreifend behandelt werden muss, über einen Delegaten (so wie im 2. Codeabschnitt).
    Jetzt möchte ich aber genau diesen Schritt in die Klasse SerialComm verschieben, damit nicht mehr die GUI Thread-sicher programmiert werden muss.
    Und an dieser Stelle komme ich nicht weiter.
    Im Netz habe ich wie gesagt nichts Sinnvolles gefunden, was von meiner ursprünglichen Lösung (in der GUI) oder meiner - nicht funktionierenden - neuen Variante (in der Klassenbib.) abweicht...

    @RodFromGermany:
    Deine Antwort hat nicht wirklich viel mit meiner Frage zu tun, vielleicht hätte ich mich klarer ausdrücken sollen:
    der Zugriff auf die Serielle Schnittstelle via SerialPort findet bereits in einer Klassenbibliothek statt (deren Konstruktor auch ein Objekt der Klasse SerialPort übergeben bekommt).
    Nicht in der GUI - und das soll auch so sein.
    Das Gesamte Projekt ist in mehrere Teilprojekte aufgeteilt (Serielle Kommunikation, GUI, ...).
    Der Zugriff auf die Schnittstelle ist hier aber auch überhaupt nicht das Problem, sondern das arbeiten mit dem System.Threading.Timer, wie auch schon die Überschrift zu meiner Frage verrät...

    Die Auftrennung hat nebenbei ganz einfache Gründe:
    - die Klassenbibliothek soll sich anderweitig wiederverwenden lassen
    - Trennung von GUI und Dingen, die "unter der Haube" ablaufen
    - bei größeren Projekten sollte es meiner Ansicht nach eigentlich zum guten Programmier-Stil gehören, das Gesamtprojekt (die Solution) in einzelne Teilprojekte (Projekte) aufzugliedern.

    Gruß,

    Christoph
    von wegen Rods Hinweis sei offtopic: mag ja alles sein, was du vorbringst, aber trotzdem sollteste dir wirklich die SerialPort-Klasse im ObjectBrowser genau angucken.

    Die feuert ein Event, wenn daten kommen.

    Also ist ein Timer, der regelmäßig nach Datenaufkommen guckt, üflüssig und fehldesigned.

    Ich empfehle, das zunächst in Ordnung zu bringen, die Geschichte mit der Event-Verlagerung in den Gui-Thread ist eine annere Baustelle (wurde gestern übrigens ausführlich behandelt, aber wo, verrate ich erst später :P).

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

    c.muehlmann schrieb:

    vielleicht hätte ich mich klarer ausdrücken sollen
    Ein ganz gewichtiges und oft ignoriertes Problem von Fragestellern. ;(

    c.muehlmann schrieb:

    das ist nur auf den ersten Blick richtig,
    Das ist richtig, sogar auf den 17. Blick, denn der @ErfinderDesRades: und ich programmieren möglicherweise schon eine halbe Woche oder so länger als Du (die Betonung liegt auf möglicherweise) und wir haben auch schon dem einen oder anderen Fragesteller ein wenig helfen können.
    Und da ist (wiederum) möglicherweise genau dieses Problem auch schon einmal aufgetreten.

    MSDN schrieb:

    Falls es erforderlich ist, Elemente im Haupt-Form oder im Haupt-Control zu ändern, senden Sie Änderungsanforderungen mithilfe von Invoke zurück, der den Vorgang dann auf dem richtigen Thread ausführt.
    Also:
    Sieh Dir die Klasse SerialPort an und pack den Timer in die Schublade.
    Designe Dein Programm zunächst ohne DLLs, und wenn alles läuft, kannste immer noch überlegen, was sinnvoll ist in eine DLL auszulagern.
    Ein SerialPort jedenfalls nicht.
    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!

    big-d schrieb:

    doch sowieso wieder hinfällig, oder?
    Möglicherweise soll es einfach weitergereicht werden.
    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 ich hab prinzipiell nix gegen eine modular gestaltete Architektur.
    Und auch nix dagegen, einen SerialPort in einer Klasse in einer Dll zu verwenden.
    Und nix dagegen, ein Ereignis zu empfangen, auszuwerten, und selbst wieder ein Ereignis zu senden.

    Nur mit einem Timer nach Daten pollen, wos doch ein Event gibt, was die Daten liefert - dassis ein Design-Fehler.
    @rod:
    mein Programm designe ich absichtlich mit DLL, da eine DLL hier - auch mit SerialPort - meiner Ansicht nach Sinn macht mit Bezug auf die Struktur der Projekte
    Ursprünglich war der Code der DLL übrigends auch im selben Projekt wie die GUI, ich habe ihn erst später rausgelöst, da ich ihn derzeit in 2 Programmen verwende.
    Abgesehen davon, wird der SerialPort in der GUI instantiiert und der Klasse in der DLL nur referenziert übergeben.

    RodFromGermany schrieb:

    denn der @ErfinderDesRades: und ich programmieren möglicherweise schon eine halbe Woche oder so länger als Du (die Betonung liegt auf möglicherweise)
    Behalt doch einfach solche Sprüche für Dich, wie lang jemand schon programmiert sagt doch eh' nichts aus.

    @big-d:
    nein, leider nicht, da dann auch der neu erzeugte Event nicht Thread-sicher sein dürfte und mit Invoke oder wie auch immer gearbeitet werden muss.

    @ErfinderDesRades
    Vielleicht der hier?
    [VB 2010] Invoke aus einer DLL heraus - aber wie?
    Danke für den Hinweis!

    Bei dem SerialPort.DataReceived-Ereignis muss ich mir noch anschauen, wie sich das Ding im Detail verhält, aber ich denke hier gebe ich Euch eher recht...

    Christoph
    Versuche zu verstehen, was in einem Post gemeint / behandelt wurde. Und wenn Du der Meinung bist, Du wurdest falsch verstanden, kläre den Irrtum auf.
    Eine SerialCom in der GUI zu instanziieren und zu initialisieren und dann in einer DLL zu verwenden birgt ein gigantisches Fehlerpotenzial in sich.
    Ich würde das lassen.
    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!
    findichnich.
    Also ich mach nie was mit Serialports, aber architektonisch gesehen ists doch dasselbe, wie wenn ich etwa einen TCPClient in einer Klasse wrapper.

    Klar kann man mit Wrappern Fehler machen, aber prinzipiell ists doch ein probates Mittel, Funktionalität zu kapseln.

    ErfinderDesRades schrieb:

    findichnich.
    Dies hier ist der Fehler:

    c.muehlmann schrieb:

    Abgesehen davon, wird der SerialPort in der GUI instantiiert und der Klasse in der DLL nur referenziert übergeben.
    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!
    jo, genau so machichs mittm TcpClient im VersuchsChat

    und da ists glaub kein Fehler.
    So Dinger sind halt disposable, und daher mussman ebenfalls IDisposable im Wrapper implementieren, und eben auf Resourcenbereinigung achten - aber prinzipiell ist das imo durchaus im Rahmen der vorgesehenen Möglichkeiten.

    ErfinderDesRades schrieb:

    und da ists glaub kein Fehler.
    Nicht Fehler, sondern

    RodFromGermany schrieb:

    Fehlerpotenzial
    Unsere statischen Quellcode-Durchflöhungs-Tools hauen uns solch Zeugs um die Ohren.
    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!

    So funktionierts

    Hallo,

    es ging hier für mich schlicht nicht um die Serielle Schnittstelle.
    Was diese angeht: Hierzu schliesse ich mich dem ErfinderDesRades 1:1 an, insbesondere was IDisposable angeht.
    Und ja: es macht Sinn diese in einer Klassenbibliothek zu kapseln, aber dazu hatte ich glaube ich schon einzelne Argumente geschrieben.
    Der Port wird übrigends nicht referenziert übergeben, sehe ich gerade.
    Verwende jetzt DataReceived() statt dem Timer, mal schaun, ob das dann in der Praxis zuverlässig ist.

    Worum es mir eigentlich ging war der Punkt der Thread-Sicherheit.
    Dies wollte ich aus der GUI in die Klasse SerialComm verlagern, v.a. um das Verwenden dieser Klasse zu vereinfachen:
    Bisher war es nötig zu (fast) jedem Event zu testen, ob dieser in der GUI mit Invoke verarbeitet werden muss.
    Es ist aber viel praktischer, dies in der Klasse SerialComm in der Kommunikations-DLL zu behandeln und somit vor der GUI-Programmierung fernzuhalten.
    Insbesondere wenn die Kommunikations-DLL in meheren Anwendungen unabhängig voneinander verwendet werden soll, ist es dann nicht mehr nötig alles mehrfach implementieren zu müssen.


    mit dem Post aus dem anderen Thread funktionierts in etwa jetzt so:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class SerialComm
    2. Private dllContext As SynchronizationContext
    3. Public Event DataReceived(ByVal sender As SerialComm, ByVal senderaddress As Byte, ByVal data As Byte(), ByVal fieldintensity As Int16)
    4. Private Sub onDataReceived(ByVal data As Byte())
    5. If dllContext IsNot Nothing Then
    6. dllContext.Send(Sub() RaiseEvent DataReceived(Me, data), Nothing)
    7. Else
    8. RaiseEvent DataReceived(Me, data)
    9. End If
    10. End Sub
    11. Public Sub New(ByVal port As SerialPort)
    12. dllContext = SynchronizationContext.Current
    13. Me.Port = port
    14. AddHandler myPort.DataReceived, AddressOf read
    15. End Sub
    16. Private readData As New List(Of Byte)
    17. Private Sub read(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs)
    18. Dim buffer(-1) As Byte
    19. If (TypeOf sender Is SerialPort) Then
    20. Dim sp As SerialPort = CType(sender, SerialPort)
    21. While (sp.BytesToRead > 0)
    22. Dim n As Integer = sp.BytesToRead
    23. ReDim buffer(n - 1)
    24. sp.Read(buffer, 0, n)
    25. readData.AddRange(buffer)
    26. End While
    27. Me.read() ' Verarbeiten ...
    28. End If
    29. End Sub
    30. Private Sub read()
    31. If (readData.Count > 0) Then
    32. ' Hier wird normalerweise auf Vollständigkeit geprüft und dann Ausgewertet usw.
    33. ' onDataReceived ist dabei nur eine von vielen Möglichkeiten
    34. onDataReceived(readData.toArray())
    35. End If
    36. End Sub
    37. End Class

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „c.muehlmann“ ()

    Unsere statischen Quellcode-Durchflöhungs-Tools hauen uns solch Zeugs um die Ohren.
    na, die müssten eh mal selbst ordentlich durchgeflöht werden, eure Tools da, aber richtig ;)

    im ernst: angenommen, ein Wrapper sei nix böses - dann isses doch schnurzepieps, ob du den TcpClient im Gui instanzierst, und mit dem TcpClient dann den Wrapper.

    Oder ob du den Wrapper mit den Initialisierungs-Daten des TcpClients instanzierst, und dann instanziert halt der Wrapper den TcpClient.

    ganz schnurzepieps, nur letzterer Fall bisserl umständlicher, weil mehrere Argumente in den Wrapper zu geben sind.

    Na, im konkreten Fall VersuchsChat eben nicht schnurz, weil der Wrapper auch einen ganz anners erzeugten TcpClient wrappern können muß (Aus TcpListener.AcceptTcpClient()).
    Also konkret am VersuchsChat erweist es sich als besser, wenn der TcpClient von aussen in den Wrapper gegeben wird - da können eure Flöhe Zirkus machen wie sie wollen.

    @TE: VersuchsChat ist auch für dich vlt. interessant, weil da gehts auch um Wrapper um Kommunikations-Einheiten, und Event-feuern aussm NebenThread und so Zeugs.
    und pls! Bitte VB-Tag benutzen - aber richtig

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

    ErfinderDesRades schrieb:

    ein Wrapper sei nix böses

    c.muehlmann schrieb:

    Der Port wird übrigends nicht referenziert übergeben, sehe ich gerade.
    Klar, wenn man Port-Nummer, Boudrate und Co übergibt, ist natürlich alles in Ordnung. :thumbsup:
    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!