none
Timer RRS feed

  • Frage

  • Hallo,
    ich muss zyklisch Daten senden, bis ein Stopp Signal erfolgt.
    Dabei verwende ich einen Timer.
    Prinzipiell tut es, bin jedoch unsicher, weil viele Beispiele den Timer auf static setzen.
     // A) Warum static?
     // B) Wenn static, kann ich die Write nicht mehr aufrufen.
     //    Was ist zu tun, dass es sicher läuft. siehe ###
    Danke im Voraus.

    Grüße Sandra

    public class SerialRS232 
    {
    
    
    private  System.Timers.Timer CyclicallyTimer = null;
    
    
    public override void WriteCyclically(string data, int intervall = 1500)
    {
    	DataWriteCyclically = data;
    
    	if (ZyklischTimer == null)
    	{
    		ZyklischTimer = new System.Timers.Timer(intervall); // z.B. 1500 ms interval
    		ZyklischTimer.Enabled = true;
    		ZyklischTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnZyklischTimerFired);
    		ZyklischTimer.AutoReset = true;
    	}
    	ZyklischTimer.Start();
    }
    
    private void OnZyklischTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
    	Trace("Thread ID {0}  OnZyklischTimerFired", System.Threading.Thread.CurrentThread.ManagedThreadId);
    	Write(DataWriteCyclically); //###
    }
    
    public override void Write(string data)
    	{
    	   
    		lock (LockingWrite)
    		{
    			using (TextWriter writer = File.CreateText("C:\\Log\\SendSerial.log"))
    			{
    				writer.WriteLine(data);              
    			}   
    			MySerialPort.Write(data);
    			MySerialPort.DiscardInBuffer();
    			MySerialPort.DiscardOutBuffer();
    		}
    	}


    • Bearbeitet Sandra Bauer Montag, 3. April 2017 16:55 Format
    Montag, 3. April 2017 16:49

Antworten

  • Hallo Sandra,

    statisch (static) bedeutet, dass es nur einen Timer für die Klasse gibt, d. h. alle Instanzen sich diesen Timer teilen würden. Was Du aber bestimmt nicht willst, denn damit würden die Ereignisse bei allen Instanzen zur gleichen Zeit ausgelöst, unabhängig davon, wie viel Zeit seit dem Senden vergangen ist.

    Dein Problem liegt eher (mal wieder) an der Stelle, an der Du den Timer erzeugst. Die Instanz beim Write zu erstellen ist ziemlich ungeschickt. Der intervall Parameter bläst das Ganze unnötig auf und es könnte weitere Probleme geben wenn im Code die beiden Varianten (mit / ohne) Timeout gemischt würde.

    Erstelle die Instanz bereits im Konstruktor - dabei tut es nicht weh, wenn Du diesen Timer nie brauchst. Willst Du es abhängig steuern, verpasse der Klasse eine Timeout Eigenschaft. Besitzt die einen gütligten Wert (wie die 1500) gesetzt, wird ein Timer verwendet, ansonsten nicht.

    Schau Dir zum Vergleich die SerialPort Klasse an, die dieses bei der WriteTime Eigenschaft tut.

    Gruß Elmar

    Dienstag, 4. April 2017 06:33
    Beantworter

Alle Antworten

  • Hallo Sandra,

    statisch (static) bedeutet, dass es nur einen Timer für die Klasse gibt, d. h. alle Instanzen sich diesen Timer teilen würden. Was Du aber bestimmt nicht willst, denn damit würden die Ereignisse bei allen Instanzen zur gleichen Zeit ausgelöst, unabhängig davon, wie viel Zeit seit dem Senden vergangen ist.

    Dein Problem liegt eher (mal wieder) an der Stelle, an der Du den Timer erzeugst. Die Instanz beim Write zu erstellen ist ziemlich ungeschickt. Der intervall Parameter bläst das Ganze unnötig auf und es könnte weitere Probleme geben wenn im Code die beiden Varianten (mit / ohne) Timeout gemischt würde.

    Erstelle die Instanz bereits im Konstruktor - dabei tut es nicht weh, wenn Du diesen Timer nie brauchst. Willst Du es abhängig steuern, verpasse der Klasse eine Timeout Eigenschaft. Besitzt die einen gütligten Wert (wie die 1500) gesetzt, wird ein Timer verwendet, ansonsten nicht.

    Schau Dir zum Vergleich die SerialPort Klasse an, die dieses bei der WriteTime Eigenschaft tut.

    Gruß Elmar

    Dienstag, 4. April 2017 06:33
    Beantworter
  • Hallo Sandra,
    Elmars Beitrag ist (mal wieder) nichts hinzuzufügen.

    Solltest du allerdings Planen deinen Timer statische zu machen, solltest du die Erzeugung des Timers in den statische Konstruktor / statitischen initialisierer verschieben. Dann garantiert dir die .net Runtime eine einmalige und rechzeitige instantiierung des Timers.


    Viele Grüße Holger M. Rößler

    Dienstag, 4. April 2017 08:52
  • Hallo Holger,
    ja Danke, kannst Du das evtl. codemäßig aufzeigen. Danke im Voraus.
    Kurzum, ich wollte meiner Klasse einen Timer spendieren, dieser ruft alle n Sekunden eine Funktion auf.
    In dieser Funktion wiederum möchte ich eine Klassenfunktion aufrufen. (Write)
    Das scheitert ja, wenn man es static macht.
    Wobei man es ja nicht zwingend static machen muss, wenn jedes RS232 Objekt einen Timer hat.
    Die aufzurufende Timerfunktion hat aber eine andere ThreadID
    Was will ich?
    xxxxxxx,yyyyyyyy,ccccccc,ddddddddd
    Eine Liste von Werten, die ich zyklisch versenden muss, bis der Empfänger
    eine Antwort sendet.
    Viele Grüße  Sandra

    • Bearbeitet Sandra Bauer Mittwoch, 5. April 2017 16:52 Format
    Dienstag, 4. April 2017 19:42
  • Hallo Sandra,
    warum sollte das scheitern? 

    Du kannst ohne Probleme eine statische Methode aus einer Instanzmethode heraus aufrufen. Andersrum ist es problematischer, da du dafür eine Objektreferenz benötigst (könnte man aber z.B. als Parameter übergeben).

    Aber ehrlich gesagt, komme ich mit deinem Codebeispiel noch nicht ganz klar. Warum spielt es für dich eine Rolle, ob der EventHandler des Timers in einem anderen Thread ausgeführt wird (wird er ja!)? Das sollte ja keine Rolle spielen sofern du daraus nicht auf UI Elemente zugreifst (das würde schief gehen).

    Um es kurz zu machen. Ich würde den Timer nicht statisch machen. Aber um dir mal ein Beispiel einer sauberen, statischen Initialisierung zu geben:

    public class SerialRS232{
       private System.Timers.Timer timer;
    
       static SerialRS232() { //statischer Konstruktor
          SerialRS232.timer = new System.Timers.Timer();
       }
    
       ... //more Code
    }

    Diese Methode ist sehr sauber, hat aber einen kleinen Nachteil. Hier handelst du dir einen (sehr) kleinen Performanceverlust ein. Dafür ist aber sauber definiert WANN statische Member die in einem statischen Konstruktor initialisiert werden --> Beim ersten Zugriff auf die Klasse. Und das garantiert dir die .net Runtime. Dies ist insbesondere zum Beispiel bei Singleton-Objekten von Bedeutung.

    Ich hoffe das hilft dir weiter...


    Viele Grüße Holger M. Rößler


    Mittwoch, 5. April 2017 06:44
  • Hallo Sandra,

    zunächst: Bitte entschuldige die vielen Einträge im gestrigen (von Dir gelöschten) Beitrag. Beim Senden meiner Antwort kam es hier zu einem seltsamen Verhalten. Der Beitrag wurde nicht versendet, dafür der Vorschlage-Link mehrmalig aktiviert. Das Warum bleibt auf dieser Seite ungeklärt - hier konnte ich keine Probleme feststellen, meiner Maus geht es auch gut ;)

    Zweiter Versuch:

    Den Timer statisch zu machen, stellt immer noch keine Lösung dar. Wie bereits in der Eingangsantwort handelst Du Dir damit nur ein, dass alle Instanzen deine "RS232 Objekts" sich den gleichen Zeitgeber und dessen Ereignis teilen.

    Wenn Du von mehreren Stellen auf das "RS232 Objekt" zugreifen willst und dabei die Zeit manipulieren willst, was wieder in die Kategorie schlechtes Design fallen würde, so könntest du anstatt dessen das geteilt "RS232 Objekt" statisch machen. Die Änderung der Zeit würde dann über eine Eigenschaft erfolgen und der Timer eine private Mitgliedsvariable des "RS232 Objekts".

    Potentielle weitere Stolperfalle wäre, dass du alle Methoden (auch die Write usw.) threadsicher machen müsstest, sobald deine Methoden aus mehreren Threads auf diese "RS232 Objekt" zugreifen würden.

    Der Systems.Timer.Timer löst seine Ereignisse immer auf einem anderen Thread aus, womit deine Methode ebenfalls umgehen müssen. Was kein Problem ist, wenn man keine Wildwestzugriffe auf den GUI Thread oder andere unsaubere Dinge in seinem "RS323 Objekt" unterbringt, sondern es nur tut, was der Name sagt, also auf auf einer seriellen Schnittstelle sendet und empfängt.

    Gruß Elmar

    Mittwoch, 5. April 2017 07:53
    Beantworter
  • zunächst: Bitte entschuldige die vielen Einträge im gestrigen (von Dir gelöschten) Beitrag. Beim Senden meiner Antwort kam es hier zu einem seltsamen Verhalten.

    Hallo Elmar,

    ok, ich dachte ich hätte was falsch gemacht.

    Was will ich?
    Diesen String "xxxxxxx,yyyyyyyy,ccccccc,ddddddddd" + (char)ACK;  zyklisch (alle 500ms) senden bis die serielle Schnittstelle mit (char)DLE antwortet.

    Das wäre es.

    • Mit dem Timer konnte ich kein Referenzobjekt übergeben.
    • Mit dem static SerialRS232() { //statischer Konstruktor SerialRS232.timer = new System.Timers.Timer(); }

    kommt eine Fehlermeldung zurück.

    •  Die Beispiele bei google definieren meist den Timer als static.

    Static benötige ich ja nicht zwingend, habe ich verstanden.

    Wie macht man es denn seitens der Softwarearchitektur richtig?

    Ich will einfach bei dem Event ein Referenzobjekt mitgeben.

         ZyklischTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnZyklischTimerFired);

    Alternativ was ich nun machte ein BackgroundThread.

    Dennoch wäre für mich hilfreich, die Lösung mit dem Timer. Kann man bestimmt mal gebrauchen.

     UserInterface habe ich nicht, somit sicher einfacher. Wenn doch, wie müßte man es dann mit dem Invoke korrekt machen?

    Danke im Voraus.

    Viele Grüße Sandra

    Mittwoch, 5. April 2017 17:20
  • Um es kurz zu machen. Ich würde den Timer nicht statisch machen. Aber um dir mal ein Beispiel einer sauberen, statischen Initialisierung zu geben:

    
    

    Hallo Holger,

    ja gerne, ich will einfach die Möglichkeiten sehen, vielleicht kann man es ja mal gebrauchen.

    Wenn static, so wie angedeutet geht's auch nicht. Vielleicht kannst noch Deine Variante mit der Referenzübergabe veröffentlichen. Danke im Voraus.

    Viele Grüße Sandra

    Mittwoch, 5. April 2017 17:23
  • Hi Sandra,
    problematisch beim Timer ist, dass die Routine für das Ereignis in einem anderen Thread abgearbeitet wird. Das kann bei der Nutzung nicht threadsicherer Objekte zu Problemen führen. Bei der Arbeit mit SerialRS232 muss man berücksichtigen, dass es Zeitverzögerungen geben kann, die durch den Timer ggf. gestört werden können (Ereignis vor Ende der Abarbeitung des vorherigen Ereignisses). Aus diesen Gründen ist es empfehlenswert, dass die Ereignisroutine des Timers eine "Aufgabe" in eine threadsichere Liste (z.B. BlockingCollection) einordnet und signalisiert (z.B. mit AutoResetEvent), dass Arbeit anliegt. Der Thread, in dem SerialRS232 genutzt wird reagiert dann auf das Signal und verarbeitet die "Aufgabe". Damit ist ein threadsichere Arbeit möglich und alle Ereignisse können nacheinander verarbeitet werden.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 6. April 2017 06:50
  • Hallo Peter,

     

     >Aus diesen Gründen ist es empfehlenswert, dass die Ereignisroutine des Timers eine "Aufgabe" in

     >eine threadsichere Liste (z.B. BlockingCollection) einordnet und signalisiert

     >(z.B. mit AutoResetEvent), dass Arbeit anliegt.

     

    Dann muss ich doch im Timer Event auch was mit übergeben, sprich die Liste.

     

    Der SerialPort hat doch schon einen Eingangeventhandler.

    Zum Abarbeiten benötige ich im Endeffekt doch kein Thread.

     

    Liste

      -Write1Message 1

      -Write1Message 2

      -Write1Message 3

      -Write1Message 4

      ...

      -Write1Message N

     

     Wenn ich es so mache, benötige ich ja eine Eingangsqueue, wenn die Zuordnung passen müsste.

     

    Könntest Du das evtl. aufzeigen, wie Du das im Details meinst?

     

    Grüße Sandra

    try
    {
    	ProductsSerialPort = new SerialPort("COM" + config.Port.ToString(), (int)config.Baud, Parity.None, 8);
    	if (null != ProductsSerialPort)
    	{
    		ProductsSerialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
    		ProductsSerialPort.Open();
    		ProductsSerialPort.DiscardInBuffer();
    		ProductsSerialPort.DiscardOutBuffer();
    	}
    }
    catch (Exception ex)
    {

    Freitag, 7. April 2017 15:25