Как добавить таймер в консольное приложение C #

132

Только это - как добавить таймер в консольное приложение C #? Было бы здорово, если бы вы могли предоставить пример кода.

Йохан Бреслер
источник
16
Внимание: ответы здесь содержат ошибку, объект Timer будет собирать мусор. Ссылка на таймер должна храниться в статической переменной, чтобы гарантировать, что он продолжает работать.
Ханс Пассан
@HansPassant Похоже, вы пропустили четкое заявление в моем ответе: «Также рекомендуется всегда использовать статический (общий в VB.NET) System.Threading.Timer, если вы разрабатываете службу Windows и вам требуется, чтобы таймер периодически запускался. . Это позволит избежать, возможно, преждевременной сборки мусора вашего объекта таймера. " Если люди хотят скопировать случайный пример и использовать его вслепую, это их проблема.
Эш

Ответы:

120

Это очень хорошо, однако для имитации прохождения некоторого времени нам нужно запустить команду, которая требует времени, и это очень ясно во втором примере.

Однако стиль использования цикла for для выполнения некоторых функций навсегда требует много ресурсов устройства, и вместо этого мы можем использовать сборщик мусора, чтобы делать что-то подобное.

Мы можем видеть эту модификацию в коде из той же книги 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
Халид, это было чрезвычайно полезно. Спасибо. Console.readline () и GC.Collect были именно тем, что мне было нужно.
Сет Спирмен
9
@ Ральф Уиллгосс, Почему GC.Collect (); необходимо?
Puchacz
2
@Puchacz Я не вижу смысла звонить GC.Collect(). Собирать нечего. Это имело бы смысл, если бы GC.KeepAlive(t)назвали послеConsole.ReadLine();
газета
1
Он
прервался
1
@Khalid Al Hajami «Однако стиль использования цикла for для выполнения некоторых функций навсегда требует много ресурсов устройства, и вместо этого мы можем использовать сборщик мусора, чтобы делать что-то подобное». Это полная чушь. Сборщик мусора совершенно не важен. Вы скопировали это из книги и не понимаете, что копируете?
Эш
66

Используйте класс System.Threading.Timer.

System.Windows.Forms.Timer предназначен в первую очередь для использования в одном потоке, обычно это поток пользовательского интерфейса Windows Forms.

Существует также класс System.Timers, добавленный на ранних этапах разработки платформы .NET. Однако обычно рекомендуется использовать вместо него класс System.Threading.Timer, так как в любом случае это просто оболочка для System.Threading.Timer.

Также рекомендуется всегда использовать статический (общий в VB.NET) System.Threading.Timer, если вы разрабатываете службу Windows и вам требуется периодический запуск таймера. Это позволит избежать возможной преждевременной сборки мусора вашего объекта таймера.

Вот пример таймера в консольном приложении:

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

Из книги Джеффа Рихтера CLR Via C # . Между прочим, эта книга описывает обоснование трех типов таймеров в главе 23, настоятельно рекомендуется.

ясень
источник
Не могли бы вы предоставить немного больше информации о самом кодировании?
Йохан Бреслер,
У вас работает пример от msdn? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Эрик Туттлман,
Эрик, я не пробовал, но не было бы ничего необычного, если бы с этим возникла проблема. Я заметил, что он также пытается выполнить какую-то межпотоковую синхронизацию, это всегда область, в которой может быть сложно разобраться. Если вы можете избежать этого в своем дизайне, это всегда будет разумно.
Эш,
1
Эш - Я определенно согласен с примерами msdn. Я бы не стал сразу сбрасывать со счетов код синхронизации, если таймер работает в собственном потоке, значит, вы пишете многопоточное приложение и вам нужно знать о проблемах, связанных с синхронизацией.
Эрик Таттлман,
1
Что произойдет, если существует несколько методов, соответствующих сигнатуре делегата TimerCallback?
Ozkan
22

Вот код для создания простого таймера на одну секунду:

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

И вот результат:

    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

РЕДАКТИРОВАТЬ: никогда не рекомендуется добавлять жесткие циклы вращения в код, поскольку они потребляют циклы процессора без какой-либо выгоды. В этом случае этот цикл был добавлен только для того, чтобы остановить закрытие приложения, чтобы можно было наблюдать за действиями потока. Но для правильности и уменьшения загрузки ЦП в этот цикл был добавлен простой вызов режима сна.

jussij
источник
7
For (;;) {} вызывает 100% использование процессора.
Сет Спирмен
1
Разве это не очевидно, если у вас есть бесконечный цикл for, тогда это приведет к 100% загрузке ЦП. Чтобы исправить это, все, что вам нужно сделать, это добавить в цикл вызов сна.
2013,
3
Удивительно, сколько людей зацикливаются на том, должен ли цикл for быть циклом while и почему ЦП переходит на 100%. Поговорим о том, чтобы пропустить лес за деревьями! Азимут, я лично хотел бы знать, чем время (1) будет отличаться от бесконечного цикла for? Конечно, люди, которые пишут оптимизатор компилятора CLR, будут уверены, что эти две конструкции кода создают один и тот же код CLR?
Blake7
1
Одна из причин, по которой while (1) не работает, заключается в том, что он недействителен c #: test.cs (21,20): ошибка CS0031: постоянное значение '1' не может быть преобразовано в 'bool'
Blake7
1
Не на моей машине (win8.1, i5), всего около 20-30%, какой компьютер у вас был тогда? @SethSpearman
shinzou
17

Давайте немного повеселимся

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

Или с помощью Rx, коротко и мило:

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

for (; ; ) { }
}
Йонатан Зетуни
источник
1
действительно лучшее решение!
Дмитрий Леденцов
8
очень нечитабельно и противоречит передовой практике. Это выглядит потрясающе, но не должно использоваться в продакшене, потому что некоторые люди будут гадить и какать сами.
Петр Кула
2
Reactive Extensions (Rx) не разрабатывался активно 2 года. Кроме того, примеры не имеют контекста и сбивают с толку. Мало знать диаграммы или примеры потоков.
Джеймс Бэйли,
4

Вы также можете использовать свои собственные механизмы синхронизации, если хотите немного большего контроля, но, возможно, меньшей точности и большего количества кода / сложности, но я все же рекомендую таймер. Однако используйте это, если вам нужно контролировать фактический поток синхронизации:

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

будет вашим временным потоком (измените его, чтобы он останавливался при необходимости и в любом временном интервале, который вы хотите).

и для использования / запуска вы можете:

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

t.Start((Action)CallBack);

Обратный вызов - это ваш метод void без параметров, который вы хотите вызывать в каждом интервале. Например:

private void CallBack()
{
    //Do Something.
}
mattlant
источник
1
Если я хочу запустить пакетное задание до истечения времени ожидания, будет ли ваше предложение лучшим?
Йохан Бреслер,
1

Вы также можете создать свой собственный (если вас не устраивают доступные варианты).

Создание собственной Timerреализации - довольно простая вещь.

Это пример приложения, которому требовался доступ к COM-объекту в том же потоке, что и остальная часть моей кодовой базы.

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

}

Вы можете добавлять события следующим образом:

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

Чтобы убедиться, что таймер работает, вам нужно создать бесконечный цикл следующим образом:

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

В C # 5.0+ и .NET Framework 4.5+ вы можете использовать async / await:

async void RunMethodEvery(Action method, double seconds)
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(seconds));
        method();
    }
 }
kokabi
источник
0

доктор

Вот и все :)

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
источник
0

Я предлагаю вам следовать рекомендациям Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Я сначала попробовал использовать System.Threading;с

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

но он постоянно останавливался примерно через 20 минут.

С этим я попробовал настройку решений

GC.KeepAlive(myTimer)

или

for (; ; ) { }
}

но в моем случае они не работали.

Следуя документации Microsoft, он работал отлично:

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 
Алессио Ди Сальво
источник