Laden...

[gelöst] Funktion von Timer starten lassen, aber nur 1x gleichzeitig

Erstellt von campos vor 14 Jahren Letzter Beitrag vor 14 Jahren 10.016 Views
C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren
[gelöst] Funktion von Timer starten lassen, aber nur 1x gleichzeitig

Hallo zusammen,habe folgenden Problem. Ich würde gerne sowas etwas wie eine Endlosschleife bauen. Das Programm ist im ersten Anlauf kein Service, soll aber so ähnlich funktionieren, es läuft im Hintergrund und fragt jede xx Sekunden ein paar Zustände ab. Meine erste Idee war die sleep Funktion zu nehmen, aber davon habe ich ziemlich schnell Abstand genommen und nehme nun die Timer Klasse, die alle xx Sekunden einen Event feuert. Im folgenden grob dargestellt mein bisheriges Formular:

 public form1()
{
//
//allgemeine Definitionen des Formulars...
//
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.Elapsed += new System.Timers.ElapsedEventHandler(ConditionLoop);
myTimer.Interval = 1000;
myTimer.Start();
}

void ConditionLoop(object sender, EventArgs e)
{
if (condition_1 == true)
{
//mach was
}
else
{
//mach was anderes
}
}

Generell sollen die Funktion ConditionLoop nur einmal gleichzeitig laufen, dass heisst, sollte ein neues Event gefeuert werden, sollte auf das vorherige Event gewartet werden, bevor die Funktion erneut aufgerufen wird. Je nachdem welche Kondition bei der if Abfrage eintritt, kann die Bearbeitungszeit höher sein, als die Wartezeit, ich habe die Timer Zeit einfach mal hochgefahren und alles läuft wunderbar, allerdings sind z.B. 5sek zu hoch, würde eigentlich gerne bei 1sek bleiben, unabhänig davon wie schnell der Prozessor arbeitet oder sonstigen Randbedingungen.
Also lange Rede kurzer Sinn, gibt es einen Befehl, der regelt, dass die Funktion nur einmal ausgeführt wird? Oder hat jeman einen komplett anderen Vorschlag.

Gelöschter Account
vor 14 Jahren

für deinen fall besser ist das autoresetevent. damit kannst du nach ablauf deiner methode einstellen, wie lange nun gewartet werden soll bis die methode wieder aufgerufen wird. so stellst du zusätzlich auch sicher, das die methode immer nur einmal gleichzeitig läuft (solange du sie nicht manuell 2 mal aufrufst 😃 )

6.862 Beiträge seit 2003
vor 14 Jahren

Hallo,

du könntest dir mit ner bool Variablen behelfen die dir anzeigt ob die Funktion noch läuft.

Sprich einfach sowas:


void ConditionLoop(object sender, EventArgs e)
{
    if(!still_running){
        still_running = true;
        if (condition_1 == true)
        {
            //mach was
        }
        else
        {
            //mach was anderes
        }
        still_running = false;
    }
}

Der Timer ruft wie gewohnt den Elapsed Handler auf, aber wenn der Code noch läuft, dann wird nichts gemacht und der Handler sofort wieder verlassen.

Baka wa shinanakya naoranai.

Mein XING Profil.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo JAck30lena,

wie stellst du dir das vor? Läuft das nicht darauf hinaus, dass AutoResetEvent.WaitOne also Thread.Sleep-Ersatz gebraucht wird?

Hallo talla,

if(!still_running){  
        still_running = true;  

was allerdings nicht 100%ig thread-safe ist. Auch wenn es bei einem Timer-Interval von einer Sekunde (und wenn die die bool-Variable volatile ist) sehr unwahrscheinlich ist, dass es tatsächlich zu Problemen kommt, würde ich das Vorgehen nicht empfehlen.

Hallo campos,

wenn du einen extra Thread startest und in diesem einem System.Windows.Forms-Timer, dann hast du automatisch, was du willst: Nur ein Tick-Event zur Zeit, egal wie lange der Tick-EventHandler läuft. Der Thread muss nach dem Starten des Timers Application.Run ausführen. Es ist nicht nötig, dass es sich bei der Anwendung um eine Windows-Forms-Anwendung handelt.

herbivore

Z
24 Beiträge seit 2009
vor 14 Jahren

Hallo zusammen

Kann man nicht auch durch setzen von True/False von System.Timers.Timer.enabled (der Timer des Elapsed-Ereignis wird durch False gestoppt und erst bei True wieder gestartet) das gleiche erreichen?


private void timer_aufruf(object sender, EventArgs e)
{
    this.calr_timer.Enabled = false;

    //Termin einlesen
    foreach (MonitorUser mUser in Parameters.monitorList)
        mUser.loadAndValidateAbsence();

    this.calr_timer.Enabled = true;
}

Gruss
David

Gelöschter Account
vor 14 Jahren

man könnte auch das autoreset des timers deaktivieren und immer am ende der methode die zeit neu berechnen und den timer damit starten.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo zino,

Kann man nicht auch durch setzen von True/False von System.Timers.Timer.enabled das gleiche erreichen?

nein, das wäre auch nicht sicher. Da ja Elapsed in einem anderen Thread läuft, wäre nicht sicher gestellt, dass das Enabled = false rechtzeitig ausgeführt werden würde.

herbivore

5.299 Beiträge seit 2008
vor 14 Jahren

Also lange Rede kurzer Sinn, gibt es einen Befehl, der regelt, dass die Funktion nur einmal ausgeführt wird? Oder hat jeman einen komplett anderen Vorschlag.

ich bevorzuge den System.Threading.Timer.
Indem man bei dem period=-1 einstellt, hat man ihn auf "one shot" konfiguriert. Und im Event mit timer.Change() die duetime bis zum nächsten shot neu setzen.

Der frühe Apfel fängt den Wurm.

456 Beiträge seit 2007
vor 14 Jahren

Es gibt auch das folgende Methoden-Attribut:


[MethodImpl(MethodImplOptions.Synchronized)]

Damit ist sichergestellt, dass die Methode immer nur einmal ausgeführt wird - ohne Ausnahmen.

Diese Vorgehensweise ist aber nicht immer, Performancegründe, zu empfehlen.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo mosspower,

MethodImplOptions.Synchronized verhindert aber nur, dass die Methode mehrfach gleichzeitig ausgeführt wird. Es würde aber nicht verhindert, dass der Timer-Event immer weiter ausgelöst wird. Im schlimmsten Fall würde also die Zahl der Timer-Events, die darauf warten, dass die synchronisierte Methode (endlich) ausgeführt wird, immer größer. So sinnvoll wie MethodImplOptions.Synchronized sein kann, im konkreten Fall würde ich es nicht verwenden.

herbivore

456 Beiträge seit 2007
vor 14 Jahren

Wenn man davon ausgeht, dass sich die Aufrufe (ggf. über einen längeren Zeitraum oder gar über die ganze Anwendungszeit hinweg) stauen, dann ist von der Methode abzuraten.

Kommt es aber nur vorübergehend zu solchen Warteschlangen, hier Anfragen minimal im Sekunden-, und nicht im Millisekundenbereich, dann kann man MethodImplOptions.Synchronized imo ohne Bedenken einsetzen.

C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

So, ich danke erst einmal für die vielen interessanten Beiträge, ich hatte ein paar Tage Auszeit und konnte deswegen nicht antworten.

Ich werde mir eure Tipps genauer ansehen und nachher dann posten, was ich umgesetzt habe.

3.971 Beiträge seit 2006
vor 14 Jahren


private const int TimerPeriod = 5000;
System.Threading.Timer m_tmr = new Timer(Timer_Tick, null, 0, TimerPeriod);

private void Timer_Tick(object state) {
  //edit:   m_tmr.Change(0, Timeout.Infinite);
  m_tmr.Change(Timeout.Infinite, Timeout.Infinite);
  try {
    ...
  }
  finally {
    m_tmr.Change(0, TimerPeriod);
  }
}

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo kleines_eichhoernchen,

auf den ersten Blick scheint mir das so, als würde da der gleiche Einwand greifen, wie ich ihn gegen tallas Vorschlag ins Feld geführt habe.

herbivore

3.971 Beiträge seit 2006
vor 14 Jahren

hast recht Herbivore,
so besser?


private const int  TimerPeriod = 5000;
System.Threading.Timer m_tmr = new Timer(Timer_Tick, null, 0, Timeout.Infinite);

private void Timer_Tick(object state) {
  try {
    ...
  }
  finally {
    m_tmr.Change(TimerPeriod, Timeout.Infinite);
  }
}

vllt. kurz zur Erklärung,
durch die Angabe von 0 und Infinite wird gesagt, der Timer startet sofort allerdings wird nicht wiederholt. Mittels Change gebe ich an, wann das nächste mal der Timer tickt, allerdings wieder ohne periodische Ausführung.

Im oberen Beispiel war auch ein Fehler drin.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo kleines_eichhoernchen,

so besser?

ja, klingt besser. 😃

herbivore

C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

Hallo Kleines_eichhoernchen,

ich habe deinen Codevroschlag ausprobiert, allerdings hänge ich an einem Problem, welches nur indirekt mit deinem Vorschlag zu tun hat. Also hier noch einmal dein Code

 private const int TimerPeriod = 5000;
System.Threading.Timer m_tmr = new System.Threading.Timer(Timer_Tick, null, 0, Timeout.Infinite);


private void Timer_Tick(object state) {
try {
...
}
finally {
m_tmr.Change(TimerPeriod, Timeout.Infinite);
}
}

Und hier der Fehler, den ich bekomme:

Error 1 A field initializer cannot reference the non-static field, method, or property 'Form1.Timer_Tick(object)'

Also grundsätzlich weiss ich, dass dieser Fehler auftritt, je nachdem wo man in einer Klasse Variablen initialisiert, allerdings verstehe ich hier nicht genau, was ich ändern sollte.

Hat jemand einen Vorschlag?

3.971 Beiträge seit 2006
vor 14 Jahren

Schreib die Zuweisung des Timer-Objektes mal in den Konstruktor von Form1

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

So, ich habe etwas rumprobiert, leider habe ich den Codevorschlag von kleines_eichhoernchen so nicht zum laufen gebracht. Habe mir bei msdn die timer Referenzseite genauer angeschaut und mir dann aus beiden Vorschlägen ein laufendes Beispielprogramm gebastelt.

Also nach meinen ersten Tests meine ich, dass dieses Programm genau das macht, was ich wollte, sollte jemand aber noch Fehler oder evtl. Ablauffehler sehen, würde mich das natürlich interessieren.

Hier mein Code:


using System;
using System.Threading;

class TimerState
{
public int counter = 0;
public Timer tmr;
}

class App
{
private const int TimerPeriod = 1000;
public static void Main()
{
TimerState s = new TimerState();
// Create the delegate that invokes methods for the timer.
TimerCallback timerDelegate = new TimerCallback(CheckStatus);

//creates the timer
Timer timer = new Timer(timerDelegate, s, 0, Timeout.Infinite);

// Keep a handle to the timer, so it can be disposed.
s.tmr = timer;

// The main thread does nothing until the timer is disposed.
while (s.tmr != null)
Thread.Sleep(0);
}

// The following method is called by the timer's delegate.

static void CheckStatus(Object state)
{
TimerState s = (TimerState)state;
try {
Console.WriteLine("AUSGEFÜHRT";);
Console.WriteLine(" Zeit {0}.",
DateTime.Now.ToString("h:mm:ss.fff";));
}
finally {
s.tmr.Change(TimerPeriod, Timeout.Infinite);
}
}


Noch kurz ne Frage zur Formatierung des Codes hier; warum werden meine Tabs nicht übernommen, um Programmteile einzurücken?

C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

Also, wie ich bereits geschrieben habe, läuft der oben stehende Code einwandfrei, allerdings mit einem kleinen, nicht zu vernachlässigendem Manko, die CPU Auslastung des Programmes liegt bei 50%. Da ich einen DualCore habe, heisst das, dass das Programm konstant einen Prozessorkern belegt.

Auch wenn ich die TimePeriod hochsetze (z.B. auf 10000) bleibt die Auslastung die ganze Zeit konstant.

Kann sich das jemand erklären?

Gelöschter Account
vor 14 Jahren

klar kann man das erklären:

// The main thread does nothing *but grabs all the unused cpu time* until the timer is disposed.
while (s.tmr != null)
Thread.Sleep(0);
}
C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

😦

Entschuldigt diesen Fehler, blöder copy and paste Fehler und auch noch im Kommentar erklärt.

Trotzdem danke für den Hinweis und die ganze Hilfe!

Beitrag gelöst!

3.971 Beiträge seit 2006
vor 14 Jahren

// The main thread does nothing *but grabs all the unused cpu time* until the timer is disposed.
while (s.tmr != null)
Thread.Sleep(0);
}

Wenn du sowas brauchst, dann kapsel deinen Timer+Tick in eine seperate Klasse und verwende ein WaitHandle (ManualResetEvent).

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

C
campos Themenstarter:in
9 Beiträge seit 2010
vor 14 Jahren

Wenn du sowas brauchst, dann kapsel deinen Timer+Tick in eine seperate Klasse und verwende ein WaitHandle (ManualResetEvent)

Ich brauche diese Warteschleife nicht, es war tatsächlich ein reiner copy paste fehler aus einer Vorlage, die ich benutzt habe. Sollte nicht passieren, habe es aber irgendwie komplett überlesen.

5.299 Beiträge seit 2008
vor 14 Jahren

Jo, die msdn-Beispiele sind zwar alle lauffähig, aber zu großen Teilen grauenhafte Antipattern. Immer mit Vorsicht zu genießen.

Du frugst übrigens nach der Formatierung: In meiner IDE ist iwo "Tabs mit Spaces auffüllen" eingestellt, d.h. wenn ich Tab drücke, kriegich 3 Spaces (hab mir TabSize 3 eingestellt).
Und bei jeder Reformatierung werden evtl. gefundene Tabs ersetzt. Das ist glaub eine sehr verbreitete Einstellung, und ich könnte mir vorstellen, die ForenSoftware kommt mit richtigen Tabs einfach nicht klar.

Der frühe Apfel fängt den Wurm.

G
46 Beiträge seit 2010
vor 14 Jahren

Hallo,
geht es nicht darum, die zweimalige Ausführung zu verhindern?
Also sowas hier (ungetestet!)


private Object lockObject;
private Monitor monitor;
public form1()
{
// Initialisierung des monitors und lockObjects:
monitor = new Monitor();
lockObject = new Object();
//
//allgemeine Definitionen des Formulars...
//
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.Elapsed += new System.Timers.ElapsedEventHandler(ConditionLoop);
myTimer.Interval = 1000;
myTimer.Start();
}

void ConditionLoop(object sender, EventArgs e)
{
if (monitor.TryEnter(lockObject))
{
if (condition_1 == true)
{
//mach was
}
else
{
//mach was anderes
}
}
} 

Damit würde man halt schlimmstenfalls einen Timerzyklus bis zur nächsten Ausführung warten. Aber so wie ich das verstehe ist das kein Problem?

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Gwar,

geht es nicht darum, die zweimalige Ausführung zu verhindern?

ja, darum ging es in dem Thread. Allerdings ist das Problem schon gelöst worden. Weiter oben finden sich mehrerer gute Lösungen. 😃

Bei deinem Vorschlag fehlt mindestens noch ein Monitor.Exit, das auch sicher ausgeführt wird, wenn das Enter geklappt hat.

herbivore

G
46 Beiträge seit 2010
vor 14 Jahren

Hallo herbivore

Allerdings ist das Problem schon gelöst worden.

das stimmt - die entsprechende Markierung des Threads ist mir leider erst nach dem Post aufgefallen 🙁 Daher wollte ich noch einen Denkansatz beisteuern...

Bei deinem Vorschlag fehlt mindestens noch ein Monitor.Exit, das auch sicher ausgeführt wird, wenn das Enter geklappt hat.

Auch das ist richtig. Dies müsste (der Vollständigkeit halber) dann entsprechend geändert werden:


void ConditionLoop(object sender, EventArgs e)
{
  if (monitor.TryEnter(lockObject))
  {
    try
    {
      if (condition_1 == true)
      {
        //mach was
      }
      else
      {
        //mach was anderes
      }
    }
    finally
    {
      monitor.Exit();
    }
  }
} 

Gruß