Як додати таймер до консольної програми C #


132

Просто так - як додати таймер до консольного додатка C #? Було б чудово, якби ви могли надати приклад кодування.


16
Застереження: відповіді тут мають помилку, об'єкт Timer збирає сміття. Посилання на таймер має зберігатися в статичній змінній, щоб забезпечити, що він продовжує тикати.
Ганс Пасант

@HansPassant Ви, здається, у своїй відповіді пропустили чітке твердження: "Також рекомендується завжди використовувати статичну (поділяється у VB.NET) System.Threading.Timer, якщо ви розробляєте службу Windows і вимагаєте періодичного запуску таймера. . Це дозволить уникнути передчасного збирання сміття об’єкта таймера ". Якщо люди хочуть скопіювати випадковий приклад і сліпо їх використовувати, це їхня проблема.
Еш

Відповіді:


120

Це дуже приємно, однак для того, щоб імітувати деякий час, нам потрібно запустити команду, яка потребує певного часу, і це зрозуміло у другому прикладі.

Однак стиль використання циклу for цикл, щоб виконувати певну функціональність назавжди, займає багато ресурсів пристрою, і замість цього ми можемо використовувати Garbage Collector, щоб зробити щось подібне.

Цю модифікацію ми можемо побачити в коді з тієї ж книги 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
@Ralph Willgoss, Чому GC.Collect (); необхідно?
Пучач

2
@Puchacz Я не бачу сенсу дзвонити GC.Collect(). Збирати нічого немає. Було б сенс, якби GC.KeepAlive(t)викликали післяConsole.ReadLine();
newprint

1
Він припинився після першого зворотного дзвінка
BadPiggie

1
@Khalid Al Hajami "Однак стиль використання циклу для виконання певної функціональності назавжди вимагає багато ресурсів пристрою. Натомість ми можемо використовувати Garbage Collector, щоб зробити щось подібне". Це абсолютно безглузде сміття. Збір сміття абсолютно не має значення. Ви скопіювали це з книги і не зрозуміли, що ви копіювали?
Ясен

65

Використовуйте клас 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
Ash - я безумовно згоден з прикладів msdn. Я б не одразу знижував код синхронізації, хоча, якщо таймер працює у власному потоці, ви пишете багатопотокове додаток і вам потрібно знати, що стосується синхронізації.
Ерік Тутлман

1
Що станеться, якщо існує кілька методів, які відповідають підпису делегата TimerCallback?
Озкан

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

EDIT: Ніколи не є ідеєю додавати жорсткі петлі спіну в код, оскільки вони споживають цикли процесора без жодних вигод. У цьому випадку цикл додавали лише для того, щоб зупинити програму не закриватися, дозволяючи спостерігати за діями потоку. Але задля правильності та зменшення використання процесора до цього циклу було додано простий дзвінок Sleep.


7
Функція for (;;) {} викликає 100% використання процесора.
Сет Спірмен

1
Хіба це не очевидно, якщо у вас є нескінченний цикл, то це призведе до 100% ЦП. Щоб вирішити, що все, що вам потрібно зробити, - це додати цикл сну до циклу.
veight

3
Дивно, скільки людей орієнтуються на те, чи повинен цикл for бути циклом часу, і чому процесор переходить на 100%. Говоріть про сумуючу деревину за деревами! Азимут, я особисто хотів би знати, як якийсь час (1) буде відрізнятися від нескінченності для циклу? Напевно люди, які пишуть оптимізатор компілятора CLR, переконаються, що ці дві конструкції коду створюють той самий код CLR?
Blake7

1
Однією з причин, поки функція (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
дуже нечитабельний і проти кращої практики. Це виглядає приголомшливо, але його не слід застосовувати у виробництві, оскільки деякі ppl будуть працювати wtf та poo самі.
Пьотр Кула

2
Реактивне розширення (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);

Зворотний виклик - це недійсний метод без параметрів, який потрібно викликати на кожному інтервалі. Наприклад:

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

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 / wait:

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

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

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 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.