Como você adiciona um timer a um aplicativo de console C #


132

Apenas isso - como você adiciona um timer a um aplicativo de console em C #? Seria ótimo se você pudesse fornecer alguns exemplos de codificação.


16
Cuidado: as respostas aqui têm um erro, o objeto Timer será coletado como lixo. A referência ao cronômetro deve ser armazenada em uma variável estática para garantir que ele continue funcionando.
Hans Passant

@HansPassant Parece que você perdeu a declaração clara na minha resposta: "Também é recomendável usar sempre um System.Threading.Timer estático (compartilhado no VB.NET) se você estiver desenvolvendo um serviço do Windows e precisar de um timer para executar periodicamente. Isso evitará a coleta de lixo possivelmente prematura do seu objeto timer. " Se as pessoas querem copiar um exemplo aleatório e usá-lo às cegas, é problema deles.
Ash

Respostas:


120

Isso é muito bom, no entanto, para simular algum tempo, precisamos executar um comando que leva algum tempo e isso é muito claro no segundo exemplo.

No entanto, o estilo de usar um loop for para executar algumas funcionalidades sempre exige muitos recursos do dispositivo. Em vez disso, podemos usar o Garbage Collector para fazer algo assim.

Podemos ver essa modificação no código do mesmo livro 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, isso foi extremamente útil. Obrigado. O console.readline () e o GC.Collect eram exatamente o que eu precisava.
Seth Spearman

9
@ Ralph Willgoss, Por que GC.Collect (); É necessário?
Puchacz

2
@Puchacz Não vejo razão para ligar GC.Collect(). Não há nada a cobrar. Faria sentido, se GC.KeepAlive(t)fosse chamado depoisConsole.ReadLine();
newprint

1
Terminou após o primeiro retorno de chamada
BadPiggie 18/09/18

1
@Khalid Al Hajami "No entanto, o estilo de usar um loop for para executar alguma funcionalidade requer sempre muitos recursos do dispositivo. Em vez disso, podemos usar o Garbage Collector para fazer algo assim." Isso é um absurdo absurdo. O coletor de lixo é totalmente irrelevante. Você copiou isso de um livro e não entendeu o que estava copiando?
Ash

65

Use a classe System.Threading.Timer.

System.Windows.Forms.Timer foi projetado principalmente para uso em um único thread, geralmente o thread da UI do Windows Forms.

Há também uma classe System.Timers adicionada no início do desenvolvimento da estrutura .NET. No entanto, geralmente é recomendável usar a classe System.Threading.Timer, pois isso é apenas um invólucro em torno de System.Threading.Timer de qualquer maneira.

Também é recomendável usar sempre um System.Threading.Timer estático (compartilhado no VB.NET) se você estiver desenvolvendo um Serviço do Windows e precisar de um timer para executar periodicamente. Isso evitará a coleta de lixo possivelmente prematura do seu objeto de timer.

Aqui está um exemplo de um timer em um aplicativo de 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 
    }
}

Do livro CLR Via C # de Jeff Richter. A propósito, este livro descreve a lógica por trás dos 3 tipos de temporizadores no Capítulo 23, altamente recomendados.


Você pode fornecer um pouco mais de informação sobre a codificação real?
Johan Bresler 9/10/08


Eric, eu não tentei, mas não seria incomum se houvesse um problema. Percebo que ele também está tentando fazer algum tipo de sincronização entre threads, essa também é uma área que pode ser complicada de acertar. Se você pode evitá-lo em seu design, é sempre inteligente fazer isso.
Ash

1
Ash - Eu definitivamente concordo com exemplos de msdn. Porém, eu não descartaria imediatamente o código de sincronização, se o temporizador for executado em seu próprio encadeamento, você estará escrevendo um aplicativo com vários encadeamentos e precisará estar ciente dos problemas relacionados à sincronização.
Eric Tuttleman 9/10/08

1
O que acontece se houver vários métodos que correspondam à assinatura do representante TimerCallback?
Ozkan

22

Aqui está o código para criar um simples temporizador de um 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);
          }
      }
  }

E aqui está a saída 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

EDIT: nunca é uma boa idéia adicionar loops de rotação rígidos no código, pois eles consomem ciclos da CPU sem nenhum ganho. Nesse caso, esse loop foi adicionado apenas para impedir o fechamento do aplicativo, permitindo que as ações do thread fossem observadas. Mas, por uma questão de correção e para reduzir o uso da CPU, uma simples chamada de suspensão foi adicionada a esse loop.


7
O for (;;) {} causa 100% de uso da CPU.
Seth Spearman

1
Não é óbvio que se você tiver um loop for infinito, isso resultará em uma CPU de 100%. Para corrigir isso, tudo o que você precisa fazer é adicionar uma chamada de suspensão ao loop.
veight

3
É incrível quantas pessoas estão concentradas em saber se o loop for deve ser um while e por que a CPU chega a 100%. Falar sobre perder a madeira para as árvores! Azimute, eu pessoalmente gostaria de saber como um tempo (1) seria diferente do loop for infinito? Certamente as pessoas que escrevem o otimizador do compilador CLR garantirão que essas duas construções de código criem exatamente o mesmo código CLR?
Blake7

1
Uma razão pela qual while (1) não vai funcionar é que não é válido c #: test.cs (21,20): CS0031 de erro: Valor constante '1' não pode ser convertido para um 'bool'
Blake7

1
Não na minha máquina (win8.1, i5), apenas cerca de 20 a 30%, que tipo de computador você tinha naquela época? @SethSpearman
shinzou

17

Vamos nos divertir um pouco

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

Ou usando Rx, curto e doce:

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

for (; ; ) { }
}

1
a melhor solução, realmente!
Dmitry Ledentsov

8
ilegível e contra as melhores práticas. Parece incrível, mas não deve ser usado na produção, porque algumas pessoas vão pesar e cocô.
Piotr Kula

2
As Extensões Reativas (Rx) não são desenvolvidas ativamente há 2 anos. Além disso, os exemplos são sem contexto e confusos. Pouco para saber diagramas ou exemplos de fluxo.
James Bailey

4

Você também pode usar seus próprios mecanismos de tempo, se quiser um pouco mais de controle, mas possivelmente menos precisão e mais código / complexidade, mas eu ainda recomendaria um timer. Use isso se precisar ter controle sobre o encadeamento de tempo real:

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

seria seu encadeamento de tempo (modifique-o para parar quando necessário, e no intervalo de tempo que você desejar).

e para usar / iniciar, você pode fazer:

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

t.Start((Action)CallBack);

O retorno de chamada é o método nulo e sem parâmetros que você deseja chamar a cada intervalo. Por exemplo:

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

1
Se eu quiser executar um trabalho em lotes até o tempo limite expirar, sua sugestão aqui será a melhor?
Johan Bresler 9/10/08

1

Você também pode criar o seu próprio (se não estiver satisfeito com as opções disponíveis).

Criar sua própria Timerimplementação é algo bastante básico.

Este é um exemplo para um aplicativo que precisava de acesso a objetos COM no mesmo thread que o restante da minha 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();
    }

}

Você pode adicionar eventos da seguinte maneira:

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 garantir que o timer funcione, é necessário criar um loop infinito da seguinte maneira:

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

No C # 5.0+ e no .NET Framework 4.5+, você pode usar async / waitit:

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

0

doc

Aí está :)

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

Sugiro que você siga as diretrizes da Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Eu tentei usar System.Threading;com

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

mas parou continuamente após ~ 20 minutos.

Com isso, tentei a configuração de soluções

GC.KeepAlive(myTimer)

ou

for (; ; ) { }
}

mas eles não funcionaram no meu caso.

Após a documentação da Microsoft, funcionou perfeitamente:

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 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.