Wie füge ich einer C # -Konsolenanwendung einen Timer hinzu?

132

Genau das - Wie fügt man einer C # -Konsolenanwendung einen Timer hinzu? Es wäre großartig, wenn Sie eine Beispielcodierung liefern könnten.

Johan Bresler
quelle
16
Achtung: Die Antworten hier haben einen Fehler, das Timer-Objekt wird Müll sammeln. Der Verweis auf den Timer muss in einer statischen Variablen gespeichert werden, um sicherzustellen, dass er weiter tickt.
Hans Passant
@HansPassant Sie scheinen die klare Aussage in meiner Antwort übersehen zu haben: "Es wird auch empfohlen, immer ein statisches (in VB.NET freigegebenes) System.Threading.Timer zu verwenden, wenn Sie einen Windows-Dienst entwickeln und einen Timer benötigen, der regelmäßig ausgeführt wird Dadurch wird möglicherweise eine vorzeitige Speicherbereinigung Ihres Timer-Objekts vermieden. " Wenn Leute ein zufälliges Beispiel kopieren und blind verwenden wollen, ist das ihr Problem.
Ash

Antworten:

120

Das ist sehr schön, aber um einige Zeit zu simulieren, müssen wir einen Befehl ausführen, der einige Zeit in Anspruch nimmt, und das ist im zweiten Beispiel sehr klar.

Der Stil, eine for-Schleife zu verwenden, um einige Funktionen für immer auszuführen, erfordert jedoch viele Geräteressourcen. Stattdessen können wir den Garbage Collector verwenden, um so etwas zu tun.

Wir können diese Änderung im Code aus demselben Buch CLR Via C # Third Ed sehen.

using System;
using System.Threading;

public static class Program {

   public static void Main() {
      // Create a Timer object that knows to call our TimerCallback
      // method once every 2000 milliseconds.
      Timer t = new Timer(TimerCallback, null, 0, 2000);
      // Wait for the user to hit <Enter>
      Console.ReadLine();
   }

   private static void TimerCallback(Object o) {
      // Display the date/time when this method got called.
      Console.WriteLine("In TimerCallback: " + DateTime.Now);
      // Force a garbage collection to occur for this demo.
      GC.Collect();
   }
}
Khalid Al Hajami
quelle
3
Khalid, das war sehr hilfreich. Vielen Dank. Die console.readline () und das GC.Collect waren genau das, was ich brauchte.
Seth Spearman
9
@ Ralph Willgoss, Warum GC.Collect (); Wird benötigt?
Puchacz
2
@Puchacz Ich sehe keinen Anlaufpunkt GC.Collect(). Es gibt nichts zu sammeln. Es wäre sinnvoll, wenn GC.KeepAlive(t)nachConsole.ReadLine();
Newprint
1
Es endete nach dem ersten Rückruf
BadPiggie
1
@Khalid Al Hajami "Der Stil, eine for-Schleife zu verwenden, um einige Funktionen für immer auszuführen, erfordert jedoch eine Menge Geräteressourcen. Stattdessen können wir den Garbage Collector verwenden, um so etwas zu tun." Das ist absolut unsinniger Müll. Der Müllsammler ist völlig irrelevant. Haben Sie dies aus einem Buch kopiert und nicht verstanden, was Sie kopiert haben?
Ash
65

Verwenden Sie die System.Threading.Timer-Klasse.

System.Windows.Forms.Timer wurde hauptsächlich für die Verwendung in einem einzelnen Thread entwickelt, normalerweise dem Windows Forms-UI-Thread.

Es gibt auch eine System.Timers-Klasse, die zu Beginn der Entwicklung des .NET-Frameworks hinzugefügt wurde. Es wird jedoch generell empfohlen, stattdessen die System.Threading.Timer-Klasse zu verwenden, da dies ohnehin nur ein Wrapper um System.Threading.Timer ist.

Es wird außerdem empfohlen, immer ein statisches (in VB.NET freigegebenes) System.Threading.Timer zu verwenden, wenn Sie einen Windows-Dienst entwickeln und einen Timer benötigen, der regelmäßig ausgeführt wird. Dadurch wird möglicherweise eine vorzeitige Speicherbereinigung Ihres Timer-Objekts vermieden.

Hier ist ein Beispiel für einen Timer in einer Konsolenanwendung:

using System; 
using System.Threading; 
public static class Program 
{ 
    public static void Main() 
    { 
       Console.WriteLine("Main thread: starting a timer"); 
       Timer t = new Timer(ComputeBoundOp, 5, 0, 2000); 
       Console.WriteLine("Main thread: Doing other work here...");
       Thread.Sleep(10000); // Simulating other work (10 seconds)
       t.Dispose(); // Cancel the timer now
    }
    // This method's signature must match the TimerCallback delegate
    private static void ComputeBoundOp(Object state) 
    { 
       // This method is executed by a thread pool thread 
       Console.WriteLine("In ComputeBoundOp: state={0}", state); 
       Thread.Sleep(1000); // Simulates other work (1 second)
       // When this method returns, the thread goes back 
       // to the pool and waits for another task 
    }
}

Aus dem Buch CLR Via C # von Jeff Richter. Übrigens beschreibt dieses Buch die Gründe für die drei Arten von Timern in Kapitel 23, die sehr zu empfehlen sind.

Asche
quelle
Können Sie etwas mehr Informationen über die eigentliche Codierung liefern?
Johan Bresler
Funktioniert das Beispiel von msdn für Sie? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Eric Tuttleman
Eric, ich habe es nicht ausprobiert, wäre aber nicht ungewöhnlich, wenn es ein Problem damit gäbe. Ich stelle fest, dass es auch versucht, eine Art Inter-Thread-Synchronisation durchzuführen. Dies ist immer ein Bereich, der schwierig sein kann, es richtig zu machen. Wenn Sie dies in Ihrem Design vermeiden können, ist es immer klug, dies zu tun.
Ash
1
Ash - Ich stimme definitiv mit MSDN-Beispielen überein. Ich würde den Synchronisationscode jedoch nicht sofort rabattieren. Wenn der Timmer in einem eigenen Thread ausgeführt wird, schreiben Sie eine Multithread-App und müssen sich der Probleme im Zusammenhang mit der Synchronisation bewusst sein.
Eric Tuttleman
1
Was passiert, wenn mehrere Methoden mit der Signatur des TimerCallback-Delegaten übereinstimmen?
Ozkan
22

Hier ist der Code zum Erstellen eines einfachen Timer-Ticks von einer Sekunde:

  using System;
  using System.Threading;

  class TimerExample
  {
      static public void Tick(Object stateInfo)
      {
          Console.WriteLine("Tick: {0}", DateTime.Now.ToString("h:mm:ss"));
      }

      static void Main()
      {
          TimerCallback callback = new TimerCallback(Tick);

          Console.WriteLine("Creating timer: {0}\n", 
                             DateTime.Now.ToString("h:mm:ss"));

          // create a one second timer tick
          Timer stateTimer = new Timer(callback, null, 0, 1000);

          // loop here forever
          for (; ; )
          {
              // add a sleep for 100 mSec to reduce CPU usage
              Thread.Sleep(100);
          }
      }
  }

Und hier ist die resultierende Ausgabe:

    c:\temp>timer.exe
    Creating timer: 5:22:40

    Tick: 5:22:40
    Tick: 5:22:41
    Tick: 5:22:42
    Tick: 5:22:43
    Tick: 5:22:44
    Tick: 5:22:45
    Tick: 5:22:46
    Tick: 5:22:47

BEARBEITEN: Es ist niemals eine gute Idee, Hard-Spin-Schleifen in den Code einzufügen, da diese CPU-Zyklen ohne Gewinn verbrauchen. In diesem Fall wurde diese Schleife hinzugefügt, um das Schließen der Anwendung zu verhindern und die Aktionen des Threads zu beobachten. Aus Gründen der Korrektheit und zur Reduzierung der CPU-Auslastung wurde dieser Schleife ein einfacher Sleep-Aufruf hinzugefügt.

jussij
quelle
7
Das for (;;) {} bewirkt eine 100% ige CPU-Auslastung.
Seth Spearman
1
Ist es nicht ziemlich offensichtlich, wenn Sie eine Endlosschleife haben, führt dies zu einer CPU von 100%. Um dies zu beheben, müssen Sie der Schleife lediglich einen Sleep-Aufruf hinzufügen.
Veight
3
Es ist erstaunlich, wie viele Leute darauf fixiert sind, ob die for-Schleife eine while-Schleife sein sollte und warum die CPU auf 100% geht. Sprechen Sie über den Wald vor lauter Bäumen vermissen! Azimut, ich persönlich würde gerne wissen, wie sich eine Weile (1) von der Endlosschleife unterscheidet. Sicherlich werden die Leute, die den CLR-Compiler-Optimierer schreiben, sicherstellen, dass diese beiden Codekonstrukte genau denselben CLR-Code erstellen?
Blake7
1
Ein Grund, warum (1) nicht funktioniert, ist, dass es nicht gültig ist. C #: test.cs (21,20): Fehler CS0031: Der konstante Wert '1' kann nicht in einen 'Bool' konvertiert werden
Blake7
1
Nicht auf meinem Computer (win8.1, i5), nur etwa 20-30%. Welche Art von Computer hatten Sie damals? @ SethSpearman
Shinzou
17

Lass uns ein bisschen Spaß haben

using System;
using System.Timers;

namespace TimerExample
{
    class Program
    {
        static Timer timer = new Timer(1000);
        static int i = 10;

        static void Main(string[] args)
        {            
            timer.Elapsed+=timer_Elapsed;
            timer.Start(); Console.Read();
        }

        private static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            i--;

            Console.Clear();
            Console.WriteLine("=================================================");
            Console.WriteLine("                  DEFUSE THE BOMB");
            Console.WriteLine(""); 
            Console.WriteLine("                Time Remaining:  " + i.ToString());
            Console.WriteLine("");        
            Console.WriteLine("=================================================");

            if (i == 0) 
            {
                Console.Clear();
                Console.WriteLine("");
                Console.WriteLine("==============================================");
                Console.WriteLine("         B O O O O O M M M M M ! ! ! !");
                Console.WriteLine("");
                Console.WriteLine("               G A M E  O V E R");
                Console.WriteLine("==============================================");

                timer.Close();
                timer.Dispose();
            }

            GC.Collect();
        }
    }
}
Echtes Caz
quelle
11

Oder mit Rx, kurz und bündig:

static void Main()
{
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(t => Console.WriteLine("I am called... {0}", t));

for (; ; ) { }
}
Yonatan Zetuny
quelle
1
Die beste Lösung, wirklich!
Dmitry Ledentsov
8
sehr unlesbar und gegen Best Practice. Es sieht fantastisch aus, sollte aber nicht in der Produktion verwendet werden, da einige Leute selbst wtf und poo gehen.
Piotr Kula
2
Reactive Extensions (Rx) wurde seit 2 Jahren nicht mehr aktiv entwickelt. Außerdem sind die Beispiele ohne Kontext und verwirrend. Wenig zu wissende Diagramme oder Flussbeispiele.
James Bailey
4

Sie können auch Ihre eigenen Timing-Mechanismen verwenden, wenn Sie etwas mehr Kontrolle, aber möglicherweise weniger Genauigkeit und mehr Code / Komplexität wünschen, aber ich würde trotzdem einen Timer empfehlen. Verwenden Sie dies jedoch, wenn Sie die Kontrolle über den tatsächlichen Timing-Thread haben müssen:

private void ThreadLoop(object callback)
{
    while(true)
    {
        ((Delegate) callback).DynamicInvoke(null);
        Thread.Sleep(5000);
    }
}

Dies wäre Ihr Timing-Thread (ändern Sie dies so, dass es bei Bedarf und in jedem gewünschten Zeitintervall stoppt).

und um / start zu verwenden, können Sie tun:

Thread t = new Thread(new ParameterizedThreadStart(ThreadLoop));

t.Start((Action)CallBack);

Callback ist Ihre void parameterlose Methode, die Sie in jedem Intervall aufrufen möchten. Beispielsweise:

private void CallBack()
{
    //Do Something.
}
mattlant
quelle
1
Wenn ich einen Batch-Job bis zum Ablauf der Zeit ausführen möchte, wäre Ihr Vorschlag hier der beste?
Johan Bresler
1

Sie können auch Ihre eigenen erstellen (wenn Sie mit den verfügbaren Optionen nicht zufrieden sind).

Das Erstellen einer eigenen TimerImplementierung ist ziemlich einfach.

Dies ist ein Beispiel für eine Anwendung, die Zugriff auf COM-Objekte im selben Thread wie der Rest meiner Codebasis benötigt.

/// <summary>
/// Internal timer for window.setTimeout() and window.setInterval().
/// This is to ensure that async calls always run on the same thread.
/// </summary>
public class Timer : IDisposable {

    public void Tick()
    {
        if (Enabled && Environment.TickCount >= nextTick)
        {
            Callback.Invoke(this, null);
            nextTick = Environment.TickCount + Interval;
        }
    }

    private int nextTick = 0;

    public void Start()
    {
        this.Enabled = true;
        Interval = interval;
    }

    public void Stop()
    {
        this.Enabled = false;
    }

    public event EventHandler Callback;

    public bool Enabled = false;

    private int interval = 1000;

    public int Interval
    {
        get { return interval; }
        set { interval = value; nextTick = Environment.TickCount + interval; }
    }

    public void Dispose()
    {
        this.Callback = null;
        this.Stop();
    }

}

Sie können Ereignisse wie folgt hinzufügen:

Timer timer = new Timer();
timer.Callback += delegate
{
    if (once) { timer.Enabled = false; }
    Callback.execute(callbackId, args);
};
timer.Enabled = true;
timer.Interval = ms;
timer.Start();
Window.timers.Add(Environment.TickCount, timer);

Um sicherzustellen, dass der Timer funktioniert, müssen Sie eine Endlosschleife wie folgt erstellen:

while (true) {
     // Create a new list in case a new timer
     // is added/removed during a callback.
     foreach (Timer timer in new List<Timer>(timers.Values))
     {
         timer.Tick();
     }
}
Steven de Salas
quelle
1

In C # 5.0+ und .NET Framework 4.5+ können Sie async / await verwenden:

async void RunMethodEvery(Action method, double seconds)
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(seconds));
        method();
    }
 }
Kokabi
quelle
0

doc

Hier hast du es :)

public static void Main()
   {
      SetTimer();

      Console.WriteLine("\nPress the Enter key to exit the application...\n");
      Console.WriteLine("The application started at {0:HH:mm:ss.fff}", DateTime.Now);
      Console.ReadLine();
      aTimer.Stop();
      aTimer.Dispose();

      Console.WriteLine("Terminating the application...");
   }

   private static void SetTimer()
   {
        // Create a timer with a two second interval.
        aTimer = new System.Timers.Timer(2000);
        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    private static void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
                          e.SignalTime);
    }
XvXLuka222
quelle
0

Ich empfehle Ihnen, die Microsoft-Richtlinien zu befolgen ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Ich versuchte zunächst mit System.Threading;mit

var myTimer = new Timer((e) =>
{
   // Code
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

aber es hörte kontinuierlich nach ~ 20 Minuten auf.

Damit habe ich die Lösungseinstellung ausprobiert

GC.KeepAlive(myTimer)

oder

for (; ; ) { }
}

aber in meinem Fall haben sie nicht funktioniert.

Nach der Microsoft-Dokumentation hat es perfekt funktioniert:

using System;
using System.Timers;

public class Example
{
    private static Timer aTimer;

    public static void Main()
    {
        // Create a timer and set a two second interval.
        aTimer = new System.Timers.Timer();
        aTimer.Interval = 2000;

        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;

        // Have the timer fire repeated events (true is the default)
        aTimer.AutoReset = true;

        // Start the timer
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program at any time... ");
        Console.ReadLine();
    }

    private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}
// The example displays output like the following: 
//       Press the Enter key to exit the program at any time... 
//       The Elapsed event was raised at 5/20/2015 8:48:58 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:00 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:02 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:04 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:06 PM 
Alessio Di Salvo
quelle