Come si aggiunge un timer a un'applicazione console C #


132

Solo questo: come si aggiunge un timer a un'applicazione console C #? Sarebbe bello se potessi fornire alcuni esempi di codifica.


16
Attenzione: le risposte qui hanno un bug, l'oggetto Timer sta per raccogliere la spazzatura. Il riferimento al timer deve essere memorizzato in una variabile statica per assicurarsi che continui a spuntare.
Hans Passant,

@HansPassant Sembra che tu abbia perso la chiara dichiarazione nella mia risposta: "Si consiglia inoltre di utilizzare sempre un System.Threading.Timer statico (condiviso in VB.NET) se si sta sviluppando un servizio di Windows e richiede un timer per funzionare periodicamente In questo modo si eviterà l'eventuale raccolta prematura di oggetti timer. " Se le persone vogliono copiare un esempio casuale e usarlo alla cieca, questo è il loro problema.
Ash,

Risposte:


120

È molto carino, tuttavia per simulare il passare del tempo è necessario eseguire un comando che richiede del tempo ed è molto chiaro nel secondo esempio.

Tuttavia, lo stile di utilizzo di un ciclo for per eseguire alcune funzionalità richiede per sempre molte risorse del dispositivo e invece possiamo usare Garbage Collector per fare qualcosa del genere.

Possiamo vedere questa modifica nel codice dallo stesso libro CLR Via C # Third Ed.

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();
   }
}

3
Khalid, questo è stato estremamente utile. Grazie. Console.readline () e GC.Collect erano proprio ciò di cui avevo bisogno.
Seth Spearman,

9
@Ralph Willgoss, Why GC.Collect (); è obbligatorio?
Puchacz,

2
@Puchacz Non vedo un punto di chiamata GC.Collect(). Non c'è niente da collezionare. Avrebbe senso, se GC.KeepAlive(t)fosse stato chiamato dopoConsole.ReadLine();
nuova stampa il

1
Terminò dopo il primo callback
BadPiggie il

1
@Khalid Al Hajami "Tuttavia, lo stile di utilizzo di un ciclo for per eseguire alcune funzionalità richiede per sempre molte risorse del dispositivo e invece possiamo usare Garbage Collector per fare qualcosa del genere." Questa è spazzatura assoluta senza senso. Il garbage collector è assolutamente irrilevante. Hai copiato questo da un libro e non hai capito cosa stavi copiando?
Ash,

65

Utilizzare la classe System.Threading.Timer.

System.Windows.Forms.Timer è progettato principalmente per l'uso in un singolo thread, in genere il thread dell'interfaccia utente di Windows Form.

Esiste anche una classe System.Timers aggiunta all'inizio dello sviluppo del framework .NET. Tuttavia, in genere si consiglia di utilizzare la classe System.Threading.Timer, poiché si tratta comunque solo di un wrapper per System.Threading.Timer.

Si consiglia inoltre di utilizzare sempre un sistema statico (condiviso in VB.NET ).Treading.Timer se si sta sviluppando un servizio di Windows e si richiede l'esecuzione periodica di un timer. Ciò eviterà l'eventuale raccolta prematura di oggetti timer.

Ecco un esempio di timer in un'applicazione console:

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 
    }
}

Dal libro CLR Via C # di Jeff Richter. A proposito, questo libro descrive la logica alla base dei 3 tipi di timer nel Capitolo 23, altamente raccomandato.


Potete fornire qualche informazione in più sulla codifica attuale?
Johan Bresler,


Eric, non l'ho provato ma non sarebbe insolito se ci fosse un problema. Ho notato che sta anche provando a fare una sorta di sincronizzazione tra thread, questa è sempre un'area che può essere difficile da ottenere. Se riesci a evitarlo nel tuo design, è sempre intelligente farlo.
Ash,

1
Ash - Sono assolutamente d'accordo sugli esempi di msdn. Tuttavia, non avrei immediatamente scartato il codice di sincronizzazione, se il timer funziona nel suo thread, allora stai scrivendo un'app multi-thread e devi essere consapevole dei problemi relativi alla sincronizzazione.
Eric Tuttleman,

1
Cosa succede se esistono più metodi che corrispondono alla firma del delegato TimerCallback?
Ozkan,

22

Ecco il codice per creare un semplice tick di un secondo:

  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);
          }
      }
  }

Ed ecco l'output risultante:

    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

EDIT: Non è mai una buona idea aggiungere loop di spin nel codice poiché consumano cicli CPU senza guadagno. In questo caso quel loop è stato aggiunto solo per impedire la chiusura dell'applicazione, consentendo di osservare le azioni del thread. Ma per motivi di correttezza e per ridurre l'utilizzo della CPU a quel loop è stata aggiunta una semplice chiamata Sleep.


7
For (;;) {} causa un utilizzo della CPU del 100%.
Seth Spearman,

1
Non è abbastanza ovvio se si dispone di un ciclo infinito per, ciò comporterà una CPU del 100%. Per risolvere tutto ciò che devi fare è aggiungere una chiamata di sonno al loop.
Veight

3
È sorprendente quante persone siano fisse sul fatto che il ciclo for debba essere un ciclo while e perché la CPU vada al 100%. Parliamo di perdere il bosco per gli alberi! Azimut, personalmente vorrei sapere come un po '(1) sarebbe diverso dall'infinito per loop? Sicuramente le persone che scrivono l'ottimizzatore del compilatore CLR si assicureranno che questi due costrutti di codice creino lo stesso identico codice CLR?
Blake7,

1
Uno dei motivi per cui mentre (1) non funzionerà è che non è valido c #: test.cs (21,20): errore CS0031: il valore costante '1' non può essere convertito in 'bool'
Blake7,

1
Non sulla mia macchina (win8.1, i5), solo circa il 20-30%, che tipo di computer avevi allora? @SethSpearman
shinzou,

17

Divertiamoci un po '

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();
        }
    }
}

11

O usando Rx, breve e dolce:

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

for (; ; ) { }
}

1
la soluzione migliore, davvero!
Dmitry Ledentsov,

8
molto illeggibile e contro le migliori pratiche. Sembra fantastico ma non dovrebbe essere usato in produzione perché un po 'di ppl andrà e cacca da solo.
Piotr Kula,

2
Reactive Extensions (Rx) non è stato sviluppato attivamente per 2 anni. Inoltre gli esempi sono senza contesto e confusi. Diagrammi o esempi di flusso poco conosciuti.
James Bailey,

4

Puoi anche usare i tuoi meccanismi di temporizzazione se vuoi un po 'più di controllo, ma forse meno precisione e più codice / complessità, ma consiglierei comunque un timer. Utilizzare questo però se è necessario avere il controllo sul thread di temporizzazione effettivo:

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

sarebbe il tuo thread di temporizzazione (modificalo per interrompere quando richiesto e in qualunque intervallo di tempo desideri).

e per usare / iniziare puoi fare:

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

t.Start((Action)CallBack);

La richiamata è il metodo vuoto senza parametri che si desidera chiamare ad ogni intervallo. Per esempio:

private void CallBack()
{
    //Do Something.
}

1
Se voglio eseguire un processo batch fino al timeout, il tuo suggerimento qui sarebbe il migliore?
Johan Bresler,

1

Puoi anche crearne uno tuo (se insoddisfatto delle opzioni disponibili).

Creare la tua Timerimplementazione è roba piuttosto semplice.

Questo è un esempio per un'applicazione che necessitava dell'accesso all'oggetto COM sullo stesso thread del resto della mia base di codice.

/// <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();
    }

}

È possibile aggiungere eventi come segue:

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);

Per assicurarsi che il timer funzioni è necessario creare un ciclo infinito come segue:

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();
     }
}

1

In C # 5.0+ e .NET Framework 4.5+ puoi usare async / waitit:

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

0

doc

Ecco qua :)

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);
    }

0

Ti consiglio di seguire le linee guida Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Prima ho provato a usare System.Threading;con

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

ma si è fermato continuamente dopo ~ 20 minuti.

Con ciò, ho provato l'impostazione delle soluzioni

GC.KeepAlive(myTimer)

o

for (; ; ) { }
}

ma non hanno funzionato nel mio caso.

A seguito della documentazione Microsoft, ha funzionato perfettamente:

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 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.