Comment ajouter un minuteur à une application console C #

132

Juste ceci - Comment ajouter une minuterie à une application console C #? Ce serait formidable si vous pouviez fournir un exemple de codage.

Johan Bresler
la source
16
Attention: les réponses ici ont un bug, l'objet Timer va être ramassé. La référence à la minuterie doit être stockée dans une variable statique pour s'assurer qu'elle continue à cocher.
Hans Passant
@HansPassant Vous semblez avoir manqué la déclaration claire dans ma réponse: "Il est également recommandé de toujours utiliser un System.Threading.Timer statique (partagé dans VB.NET) si vous développez un service Windows et que vous avez besoin d'un minuteur pour s'exécuter périodiquement . Cela évitera éventuellement un garbage collection prématuré de votre objet timer. " Si les gens veulent copier un exemple aléatoire et l'utiliser à l'aveuglette, c'est leur problème.
Ash

Réponses:

120

C'est très bien, cependant, pour simuler le temps qui passe, nous devons exécuter une commande qui prend du temps et c'est très clair dans le deuxième exemple.

Cependant, le style d'utilisation d'une boucle for pour faire certaines fonctionnalités prend pour toujours beaucoup de ressources de l'appareil et à la place, nous pouvons utiliser le Garbage Collector pour faire quelque chose comme ça.

On peut voir cette modification dans le code du même livre 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();
   }
}
Khalid Al Hajami
la source
3
Khalid, cela a été extrêmement utile. Merci. La console.readline () et le GC.Collect étaient exactement ce dont j'avais besoin.
Seth Spearman
9
@Ralph Willgoss, Pourquoi GC.Collect (); est requis?
Puchacz
2
@Puchacz Je ne vois pas l'intérêt d'appeler GC.Collect(). Il n'y a rien à collecter. Cela aurait du sens, si GC.KeepAlive(t)était appelé aprèsConsole.ReadLine();
newprint
1
Il s'est terminé après le premier rappel
BadPiggie
1
@Khalid Al Hajami "Cependant, le style d'utilisation d'une boucle for pour faire certaines fonctionnalités prend à jamais beaucoup de ressources de l'appareil et à la place nous pouvons utiliser le Garbage Collector pour faire quelque chose comme ça." Ce sont des ordures absolument absurdes. Le ramasse-miettes n'est absolument pas pertinent. Avez-vous copié ceci à partir d'un livre sans comprendre ce que vous copiez?
Ash
66

Utilisez la classe System.Threading.Timer.

System.Windows.Forms.Timer est principalement conçu pour être utilisé dans un seul thread, généralement le thread d'interface utilisateur Windows Forms.

Il existe également une classe System.Timers ajoutée au début du développement du framework .NET. Cependant, il est généralement recommandé d'utiliser la classe System.Threading.Timer à la place car il ne s'agit que d'un wrapper autour de System.Threading.Timer de toute façon.

Il est également recommandé de toujours utiliser un System.Threading.Timer statique (partagé dans VB.NET) si vous développez un service Windows et que vous avez besoin d'une minuterie pour s'exécuter périodiquement. Cela évitera éventuellement un garbage collection prématuré de votre objet timer.

Voici un exemple de minuterie dans une application 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 
    }
}

Extrait du livre CLR Via C # de Jeff Richter. En passant, ce livre décrit la logique derrière les 3 types de minuteries dans le chapitre 23, fortement recommandé.

Cendre
la source
Pouvez-vous fournir un peu plus d'informations sur le codage réel?
Johan Bresler
L'exemple de msdn fonctionne-t-il pour vous? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Eric Tuttleman
Eric, je ne l'ai pas essayé mais ce ne serait pas inhabituel s'il y avait un problème avec. Je remarque qu'il essaie également de faire une sorte de synchronisation inter-thread, c'est toujours un domaine qui peut être difficile à faire. Si vous pouvez l'éviter dans votre conception, il est toujours judicieux de le faire.
Ash
1
Ash - Je suis tout à fait d'accord avec les exemples msdn. Cependant, je n'écarterais pas immédiatement le code de synchronisation, si le minuteur s'exécute dans son propre thread, alors vous écrivez une application multithread et vous devez être conscient des problèmes liés à la synchronisation.
Eric Tuttleman
1
Que se passe-t-il s'il existe plusieurs méthodes qui correspondent à la signature du délégué TimerCallback?
Ozkan
22

Voici le code pour créer un simple tick de minuterie d'une seconde:

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

Et voici le résultat obtenu:

    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: Ce n'est jamais une bonne idée d'ajouter des boucles hard spin dans le code car elles consomment des cycles CPU sans gain. Dans ce cas, cette boucle a été ajoutée juste pour empêcher l'application de se fermer, ce qui permet d'observer les actions du thread. Mais par souci d'exactitude et pour réduire l'utilisation du processeur, un simple appel Sleep a été ajouté à cette boucle.

jussij
la source
7
Le for (;;) {} provoque une utilisation à 100% du processeur.
Seth Spearman
1
N'est-ce pas assez évident si vous avez une boucle for infinie, cela se traduira par un CPU de 100%. Pour résoudre ce problème, tout ce que vous devez faire est d'ajouter un appel de veille à la boucle.
veight
3
Il est étonnant de voir combien de personnes se demandent si la boucle for doit être une boucle while et pourquoi le processeur passe à 100%. Parlez de manquer le bois pour les arbres! Azimuth, je voudrais personnellement savoir comment un while (1) serait différent de l'infini pour la boucle? Les personnes qui écrivent l'optimiseur du compilateur CLR s'assureront-elles que ces deux constructions de code créent exactement le même code CLR?
Blake7
1
Une des raisons pour lesquelles while (1) ne fonctionnera pas est qu'il n'est pas valide c #: test.cs (21,20): erreur CS0031: La valeur constante «1» ne peut pas être convertie en «
booléen
1
Pas sur ma machine (win8.1, i5), seulement environ 20-30%, quel type d'ordinateur aviez-vous à l'époque? @SethSpearman
shinzou
17

Permet de s'amuser un peu

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();
        }
    }
}
Vrai Caz
la source
11

Ou en utilisant Rx, court et doux:

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

for (; ; ) { }
}
Yonatan Zetuny
la source
1
la meilleure solution, vraiment!
Dmitry Ledentsov
8
très illisible et contraire aux meilleures pratiques. Cela a l'air génial mais ne devrait pas être utilisé en production car certains ppl iront wtf et caca eux-mêmes.
Piotr Kula
2
Les extensions réactives (Rx) n'ont pas été activement développées depuis 2 ans. De plus, les exemples sont sans contexte et déroutants. Peu de diagrammes ou d'exemples de flux à connaître.
James Bailey
4

Vous pouvez également utiliser vos propres mécanismes de chronométrage si vous voulez un peu plus de contrôle, mais peut-être moins de précision et plus de code / complexité, mais je recommanderais quand même un minuteur. Utilisez ceci si vous avez besoin de contrôler le thread de synchronisation réel:

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

serait votre fil de synchronisation (modifiez-le pour qu'il s'arrête lorsque cela est nécessaire, et à n'importe quel intervalle de temps souhaité).

et pour utiliser / démarrer, vous pouvez faire:

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

t.Start((Action)CallBack);

Le rappel est votre méthode sans paramètre void que vous souhaitez appeler à chaque intervalle. Par exemple:

private void CallBack()
{
    //Do Something.
}
mattlant
la source
1
Si je veux exécuter un travail par lots jusqu'à ce qu'il expire, votre suggestion serait-elle la meilleure?
Johan Bresler
1

Vous pouvez également créer le vôtre (si vous n'êtes pas satisfait des options disponibles).

Créer votre propre Timerimplémentation est assez basique.

Ceci est un exemple pour une application qui avait besoin d'un accès aux objets COM sur le même thread que le reste de ma base de code.

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

}

Vous pouvez ajouter des événements comme suit:

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

Pour vous assurer que la minuterie fonctionne, vous devez créer une boucle sans fin comme suit:

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
la source
1

Dans C # 5.0+ et .NET Framework 4.5+, vous pouvez utiliser async / await:

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

doc

Voilà :)

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
la source
0

Je vous suggère de suivre les directives Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

J'ai d'abord essayé d'utiliser System.Threading;avec

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

mais il s'est arrêté en continu après ~ 20 minutes.

Avec ça, j'ai essayé le réglage des solutions

GC.KeepAlive(myTimer)

ou

for (; ; ) { }
}

mais ils n'ont pas fonctionné dans mon cas.

Suivant la documentation Microsoft, cela a parfaitement fonctionné:

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
la source