¿Cómo se agrega un temporizador a una aplicación de consola C #?

132

Solo esto: ¿cómo se agrega un temporizador a una aplicación de consola C #? Sería genial si pudiera proporcionar algún ejemplo de codificación.

Johan Bresler
fuente
16
Precaución: las respuestas aquí tienen un error, el objeto Timer se va a recolectar basura. La referencia al temporizador debe almacenarse en una variable estática para garantizar que siga marcando.
Hans Passant
@HansPassant Parece haber perdido la declaración clara en mi respuesta: "También se recomienda usar siempre un System.Threading.Timer estático (compartido en VB.NET) si está desarrollando un servicio de Windows y necesita un temporizador para ejecutarse periódicamente . Esto evitará posiblemente la recolección de basura prematura de su objeto temporizador ". Si la gente quiere copiar un ejemplo aleatorio y usarlo a ciegas, ese es su problema.
Ash

Respuestas:

120

Eso es muy bueno, sin embargo, para simular el paso del tiempo, necesitamos ejecutar un comando que tome algo de tiempo y que esté muy claro en el segundo ejemplo.

Sin embargo, el estilo de usar un bucle for para hacer alguna funcionalidad para siempre requiere muchos recursos del dispositivo y en su lugar podemos usar el recolector de basura para hacer algo así.

Podemos ver esta modificación en el código del mismo 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();
   }
}
Khalid Al Hajami
fuente
3
Khalid, esto fue extremadamente útil. Gracias. Console.readline () y GC.Collect era justo lo que necesitaba.
Seth Spearman
9
@Ralph Willgoss, Why GC.Collect (); ¿es requerido?
Puchacz
2
@ Puchacz No veo un punto de llamar GC.Collect(). No hay nada que coleccionar. Tendría sentido si GC.KeepAlive(t)se llamara despuésConsole.ReadLine();
newprint
1
Terminó después de la primera devolución de llamada
BadPiggie
1
@Khalid Al Hajami "Sin embargo, el estilo de usar un bucle for para hacer alguna funcionalidad para siempre requiere muchos recursos del dispositivo y en su lugar podemos usar el recolector de basura para hacer algo así". Esto es una basura absoluta sin sentido. El recolector de basura es completamente irrelevante. ¿Copió esto de un libro y no entendió lo que estaba copiando?
Ash
65

Use la clase System.Threading.Timer.

System.Windows.Forms.Timer está diseñado principalmente para su uso en un solo subproceso, generalmente el subproceso de interfaz de usuario de Windows Forms.

También hay una clase System.Timers agregada al inicio del desarrollo del marco .NET. Sin embargo, generalmente se recomienda usar la clase System.Threading.Timer en su lugar, ya que de todos modos esto es solo una envoltura alrededor de System.Threading.Timer.

También se recomienda usar siempre un System.Threading.Timer estático (compartido en VB.NET) si está desarrollando un servicio de Windows y necesita un temporizador para ejecutarse periódicamente. Esto evitará la recolección de basura posiblemente prematura de su objeto de temporizador.

Aquí hay un ejemplo de un temporizador en una aplicación de consola:

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

Del libro CLR Via C # de Jeff Richter. Por cierto, este libro describe la justificación detrás de los 3 tipos de temporizadores en el Capítulo 23, muy recomendable.

Ceniza
fuente
¿Puede proporcionar un poco más de información sobre la codificación real?
Johan Bresler
¿El ejemplo de msdn funciona para ti? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Eric Tuttleman
Eric, no lo he probado, pero no sería inusual si hubiera un problema. Noté que también está tratando de hacer algún tipo de sincronización entre subprocesos, esto siempre es un área que puede ser difícil de hacer bien. Si puede evitarlo en su diseño, siempre es inteligente hacerlo.
Ash
1
Ash: definitivamente estoy de acuerdo con los ejemplos de msdn. Sin embargo, no descartaría de inmediato el código de sincronización, si el temporizador se ejecuta en su propio hilo, entonces está escribiendo una aplicación multiproceso y necesita estar al tanto de los problemas relacionados con la sincronización.
Eric Tuttleman
1
¿Qué sucede si hay varios métodos que coinciden con la firma del delegado TimerCallback?
Ozkan
22

Aquí está el código para crear un simple tic de temporizador de un segundo:

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

Y aquí está el resultado resultante:

    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

EDITAR: Nunca es una buena idea agregar bucles de giro duro al código, ya que consumen ciclos de CPU sin ganancia. En este caso, ese bucle se agregó solo para detener el cierre de la aplicación, lo que permite observar las acciones del hilo. Pero en aras de la corrección y para reducir el uso de la CPU, se agregó una simple llamada de suspensión a ese bucle.

jussij
fuente
77
El for (;;) {} causa el uso del 100% de la CPU.
Seth Spearman
1
¿No es bastante obvio si tienes un bucle infinito para que eso dará como resultado una CPU del 100%? Para solucionarlo, todo lo que necesita hacer es agregar una llamada de suspensión al bucle.
veight
3
Es sorprendente cuántas personas están obsesionadas con saber si el ciclo for debería ser un ciclo while y por qué la CPU llega al 100%. ¡Habla de echar de menos la madera para los árboles! Azimut, personalmente me gustaría saber cómo un tiempo (1) sería diferente al bucle infinito. ¿Seguramente las personas que escriben el optimizador del compilador CLR se asegurarán de que estas dos construcciones de código creen exactamente el mismo código CLR?
Blake7
1
Una razón por la que mientras (1) no funcionará es que no es válido c #: test.cs (21,20): error CS0031: El valor constante '1' no se puede convertir en 'bool'
Blake7
1
No en mi máquina (win8.1, i5), solo alrededor del 20-30%, ¿qué tipo de computadora tenía en ese momento? @SethSpearman
shinzou
17

Vamos a divertirnos un poco

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();
        }
    }
}
Caz real
fuente
11

O usando Rx, corto y dulce:

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

for (; ; ) { }
}
Yonatan Zetuny
fuente
1
la mejor solución, de verdad!
Dmitry Ledentsov
8
Muy ilegible y en contra de las mejores prácticas. Se ve increíble, pero no se debe usar en la producción porque algunas personas se irán y se cagarán.
Piotr Kula
2
Las extensiones reactivas (Rx) no se han desarrollado activamente durante 2 años. Además, los ejemplos son sin contexto y confusos. Poco que saber diagramas o ejemplos de flujo.
James Bailey
4

También puede usar sus propios mecanismos de temporización si desea un poco más de control, pero posiblemente menos precisión y más código / complejidad, pero aún así recomendaría un temporizador. Sin embargo, use esto si necesita tener control sobre el hilo de sincronización real:

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

sería su hilo de sincronización (modifique esto para detener cuando sea necesario y en el intervalo de tiempo que desee).

y para usar / comenzar puedes hacer:

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

t.Start((Action)CallBack);

La devolución de llamada es su método vacío sin parámetros al que desea llamar en cada intervalo. Por ejemplo:

private void CallBack()
{
    //Do Something.
}
mate
fuente
1
Si quiero ejecutar un trabajo por lotes hasta que se agote, ¿su sugerencia aquí sería la mejor?
Johan Bresler
1

También puede crear el suyo propio (si no está satisfecho con las opciones disponibles).

Crear tu propia Timerimplementación es algo bastante básico.

Este es un ejemplo para una aplicación que necesitaba acceso a objetos COM en el mismo hilo que el resto de mi base de código.

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

}

Puede agregar eventos de la siguiente manera:

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

Para asegurarse de que el temporizador funciona, debe crear un bucle sin fin de la siguiente manera:

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
fuente
1

En C # 5.0+ y .NET Framework 4.5+ puedes usar async / await:

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

Doc

Ahí tienes :)

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
fuente
0

Le sugiero que siga las pautas de Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Primero intenté usar System.Threading;con

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

pero se detuvo continuamente después de ~ 20 minutos.

Con eso, probé la configuración de soluciones

GC.KeepAlive(myTimer)

o

for (; ; ) { }
}

pero no funcionaron en mi caso.

Siguiendo la documentación de Microsoft, funcionó perfectamente:

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
fuente