Jak dodać czasomierz do aplikacji konsoli C #

135

Tylko to - jak dodać licznik czasu do aplikacji konsoli C #? Byłoby wspaniale, gdybyś mógł podać przykładowe kodowanie.

Johan Bresler
źródło
17
Uwaga: odpowiedzi tutaj mają błąd, obiekt Timer będzie zbierał śmieci. Odniesienie do timera musi być przechowywane w zmiennej statycznej, aby zapewnić ciągłe tykanie.
Hans Passant
@HansPassant Wydaje się, że przegapiłeś jasne stwierdzenie w mojej odpowiedzi: „Zaleca się również, aby zawsze używać statycznego (udostępnionego w VB.NET) System.Threading.Timer, jeśli tworzysz usługę systemu Windows i potrzebujesz okresowego działania licznika czasu . Pozwoli to uniknąć prawdopodobnie przedwczesnego usuwania pamięci z obiektu czasowego. " Jeśli ludzie chcą skopiować losowy przykład i używać go na ślepo, to ich problem.
Ash

Odpowiedzi:

121

To bardzo miłe, jednak aby zasymulować upływ czasu, musimy uruchomić polecenie, które zajmuje trochę czasu, co jest bardzo jasne w drugim przykładzie.

Jednak styl używania pętli for do wykonywania pewnych funkcji na zawsze zajmuje dużo zasobów urządzenia, a zamiast tego możemy użyć Garbage Collectora do zrobienia czegoś takiego.

Widzimy tę modyfikację w kodzie z tej samej książki 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
źródło
3
Khalid, to było niezwykle pomocne. Dzięki. Console.readline () i GC.Collect były właśnie tym, czego potrzebowałem.
Seth Spearman,
9
@Ralph Willgoss, Why GC.Collect (); jest wymagane?
Puchacz
2
@Puchacz Nie widzę sensu dzwonienia GC.Collect(). Nie ma nic do zebrania. Miałoby to sens, gdyby GC.KeepAlive(t)nazwano go poConsole.ReadLine();
nowy druk
1
Zakończyło się po pierwszym oddzwonieniu
BadPiggie
1
@Khalid Al Hajami "Jednak styl używania pętli for do wykonywania pewnych funkcji na zawsze zabiera dużo zasobów urządzenia i zamiast tego możemy użyć Garbage Collectora do zrobienia czegoś takiego." To absolutnie bezsensowne bzdury. Odśmiecacz jest zupełnie nieistotny. Czy skopiowałeś to z książki i nie rozumiesz, co kopiujesz?
Ash
68

Użyj klasy System.Threading.Timer.

System.Windows.Forms.Timer jest przeznaczony głównie do użytku w pojedynczym wątku, zwykle w wątku interfejsu użytkownika Windows Forms.

Istnieje również klasa System.Timers dodana na wczesnym etapie rozwoju platformy .NET. Jednak generalnie zaleca się używanie zamiast tego klasy System.Threading.Timer, ponieważ i tak jest to tylko opakowanie wokół System.Threading.Timer.

Zaleca się również, aby zawsze używać statycznego (udostępnionego w VB.NET) System.Threading.Timer, jeśli tworzysz usługę systemu Windows i potrzebujesz okresowego działania licznika czasu. Pozwoli to uniknąć prawdopodobnie przedwczesnego wyrzucania elementów bezużytecznych w obiekcie zegara.

Oto przykład licznika czasu w aplikacji konsoli:

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

Z książki CLR Via C # autorstwa Jeffa Richtera. Nawiasem mówiąc, ta książka opisuje uzasadnienie trzech typów timerów w rozdziale 23, wysoce zalecane.

Popiół
źródło
Czy możesz podać trochę więcej informacji na temat faktycznego kodowania?
Johan Bresler
Czy przykład z msdn działa dla Ciebie? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Eric Tuttleman
Eric, nie próbowałem tego, ale nie byłoby to niezwykłe, gdyby był z tym problem. Zauważyłem, że próbuje również wykonać jakąś synchronizację między wątkami, jest to zawsze obszar, który może być trudny do wykonania. Jeśli możesz tego uniknąć w swoim projekcie, zawsze warto to zrobić.
Ash
1
Ash - zdecydowanie zgadzam się z przykładami msdn. Nie zdyskontowałbym jednak od razu kodu synchronizacji, jeśli zegar działa we własnym wątku, to piszesz aplikację wielowątkową i musisz być świadomy problemów związanych z synchronizacją.
Eric Tuttleman
1
Co się stanie, jeśli istnieje wiele metod, które pasują do podpisu delegata TimerCallback?
Ozkan
23

Oto kod do utworzenia prostego jednosekundowego taktu timera:

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

A oto wynikowy wynik:

    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

EDYCJA: Nigdy nie jest dobrym pomysłem dodawanie twardych pętli do kodu, ponieważ zużywają one cykle procesora bez zysku. W tym przypadku ta pętla została dodana tylko po to, aby zatrzymać zamykanie aplikacji, umożliwiając obserwację działań wątku. Jednak ze względu na poprawność i zmniejszenie wykorzystania procesora do tej pętli dodano proste wywołanie uśpienia.

jussij
źródło
7
For (;;) {} powoduje 100% użycie procesora.
Seth Spearman
1
Czy nie jest całkiem oczywiste, że jeśli masz nieskończoną pętlę for, to da to procesor w 100%. Aby to naprawić, wystarczy dodać wywołanie uśpienia do pętli.
Veight
3
To niesamowite, jak wielu ludzi jest skupionych na tym, czy pętla for powinna być pętlą while i dlaczego procesor osiąga 100%. Porozmawiaj o tęsknij za drewnem dla drzew! Azymut, osobiście chciałbym wiedzieć, jak czas while (1) różni się od nieskończonej pętli for? Z pewnością ludzie, którzy piszą optymalizator kompilatora CLR, upewnią się, że te dwie konstrukcje kodu utworzą dokładnie ten sam kod CLR?
Blake7,
1
Jednym z powodów, dla których while (1) nie zadziała, jest to, że jest nieprawidłowy c #: test.cs (21,20): błąd CS0031: Stała wartość '1' nie może być zamieniona na 'bool'
Blake7
1
Nie na moim komputerze (win8.1, i5), tylko około 20-30%, jaki komputer miałeś wtedy? @SethSpearman
shinzou
17

Bawmy się trochę

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();
        }
    }
}
Real Caz
źródło
11

Lub używając Rx, krótkie i słodkie:

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

for (; ; ) { }
}
Yonatan Zetuny
źródło
1
najlepsze rozwiązanie, naprawdę!
Dmitry Ledentsov
8
bardzo nieczytelne i sprzeczne z najlepszymi praktykami. Wygląda niesamowicie, ale nie powinien być używany w produkcji, ponieważ niektóre osoby same pójdą z powrotem i kupią.
Piotr Kula
2
Rozszerzenia reaktywne (Rx) nie były aktywnie rozwijane od 2 lat. Ponadto przykłady są pozbawione kontekstu i mylące. Niewiele wiedzieć o diagramach lub przykładach przepływu.
James Bailey
4

Możesz także użyć własnych mechanizmów czasowych, jeśli chcesz mieć trochę większą kontrolę, ale prawdopodobnie mniej dokładności i więcej kodu / złożoności, ale nadal polecałbym zegar. Użyj tego jednak, jeśli chcesz mieć kontrolę nad faktycznym wątkiem czasowym:

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

byłby twoim wątkiem czasowym (zmodyfikuj to, aby zatrzymać, gdy jest to wymagane, w dowolnym przedziale czasowym).

i aby użyć / start możesz zrobić:

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

t.Start((Action)CallBack);

Callback to metoda bez parametrów, która ma być wywoływana w każdym interwale. Na przykład:

private void CallBack()
{
    //Do Something.
}
mattlant
źródło
1
Jeśli chcę uruchomić zadanie wsadowe, dopóki nie upłynie limit czasu, czy twoja sugestia tutaj będzie najlepsza?
Johan Bresler
1

Możesz także utworzyć własny (jeśli nie jesteś zadowolony z dostępnych opcji).

Tworzenie własnej Timerimplementacji to dość prosta rzecz.

To jest przykład dla aplikacji, która potrzebowała dostępu do obiektu COM w tym samym wątku, co reszta mojej bazy kodu.

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

}

Możesz dodawać wydarzenia w następujący sposób:

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

Aby upewnić się, że stoper działa, musisz utworzyć niekończącą się pętlę w następujący sposób:

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
źródło
1

W C # 5.0+ i .NET Framework 4.5+ możesz używać async / await:

async void RunMethodEvery(Action method, double seconds)
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(seconds));
        method();
    }
 }
Ayub
źródło
0

doc

Masz to :)

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
źródło
0

Sugeruję przestrzeganie wytycznych Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 ).

Najpierw próbowałem użyć System.Threading;z

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

ale ciągle zatrzymywał się po ~ 20 minutach.

Dzięki temu wypróbowałem ustawienia rozwiązań

GC.KeepAlive(myTimer)

lub

for (; ; ) { }
}

ale nie zadziałały w moim przypadku.

Zgodnie z dokumentacją Microsoft działało idealnie:

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
źródło
0

Użyj projektu PowerConsole na Github pod adresem https://github.com/bigabdoul/PowerConsole lub równoważnego pakietu NuGet pod adresem https://www.nuget.org/packages/PowerConsole . Elegancko obsługuje timery wielokrotnego użytku. Spójrz na ten przykładowy kod:

using PowerConsole;

namespace PowerConsoleTest
{
    class Program
    {
        static readonly SmartConsole MyConsole = SmartConsole.Default;

        static void Main()
        {
            RunTimers();
        }

        public static void RunTimers()
        {
            // CAUTION: SmartConsole is not thread safe!
            // Spawn multiple timers carefully when accessing
            // simultaneously members of the SmartConsole class.

            MyConsole.WriteInfo("\nWelcome to the Timers demo!\n")

            // SetTimeout is called only once after the provided delay and
            // is automatically removed by the TimerManager class
            .SetTimeout(e =>
            {
                // this action is called back after 5.5 seconds; the name
                // of the timer is useful should we want to clear it
                // before this action gets executed
                e.Console.Write("\n").WriteError("Time out occured after 5.5 seconds! " +
                    "Timer has been automatically disposed.\n");

                // the next statement will make the current instance of 
                // SmartConsole throw an exception on the next prompt attempt
                // e.Console.CancelRequested = true;

                // use 5500 or any other value not multiple of 1000 to 
                // reduce write collision risk with the next timer
            }, millisecondsDelay: 5500, name: "SampleTimeout")

            .SetInterval(e =>
            {
                if (e.Ticks == 1)
                {
                    e.Console.WriteLine();
                }

                e.Console.Write($"\rFirst timer tick: ", System.ConsoleColor.White)
                .WriteInfo(e.TicksToSecondsElapsed());

                if (e.Ticks > 4)
                {
                    // we could remove the previous timeout:
                    // e.Console.ClearTimeout("SampleTimeout");
                }

            }, millisecondsInterval: 1000, "EverySecond")

            // we can add as many timers as we want (or the computer's resources permit)
            .SetInterval(e =>
            {
                if (e.Ticks == 1 || e.Ticks == 3) // 1.5 or 4.5 seconds to avoid write collision
                {
                    e.Console.WriteSuccess("\nSecond timer is active...\n");
                }
                else if (e.Ticks == 5)
                {
                    e.Console.WriteWarning("\nSecond timer is disposing...\n");

                    // doesn't dispose the timer
                    // e.Timer.Stop();

                    // clean up if we no longer need it
                    e.DisposeTimer();
                }
                else
                {
                    System.Diagnostics.Trace.WriteLine($"Second timer tick: {e.Ticks}");
                }
            }, 1500)
            .Prompt("\nPress Enter to stop the timers: ")
            
            // makes sure that any remaining timer is disposed off
            .ClearTimers()

            .WriteSuccess("Timers cleared!\n");
        }
    }
}
Bigabdoul
źródło