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;
publicstaticclassProgram {
publicstaticvoidMain() {
// 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();
}
privatestaticvoidTimerCallback(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, 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;
publicstaticclassProgram
{
publicstaticvoidMain()
{
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 delegateprivatestaticvoidComputeBoundOp(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.
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;
classTimerExample
{
staticpublicvoidTick(Object stateInfo)
{
Console.WriteLine("Tick: {0}", DateTime.Now.ToString("h:mm:ss"));
}
staticvoidMain()
{
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 foreverfor (; ; )
{
// add a sleep for 100 mSec to reduce CPU usage
Thread.Sleep(100);
}
}
}
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.
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;
namespaceTimerExample
{
classProgram
{
static Timer timer = new Timer(1000);
staticint i = 10;
staticvoidMain(string[] args)
{
timer.Elapsed+=timer_Elapsed;
timer.Start(); Console.Read();
}
privatestaticvoidtimer_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();
}
}
}
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:
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();
}
}
publicstaticvoidMain()
{
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...");
}
privatestaticvoidSetTimer()
{
// 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;
}
privatestaticvoidOnTimedEvent(Object source, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
e.SignalTime);
}
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;
publicclassExample
{
privatestatic Timer aTimer;
publicstaticvoidMain()
{
// 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();
}
privatestaticvoidOnTimedEvent(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
using PowerConsole;
namespacePowerConsoleTest
{
classProgram
{
staticreadonly SmartConsole MyConsole = SmartConsole.Default;
staticvoidMain()
{
RunTimers();
}
publicstaticvoidRunTimers()
{
// 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");
}
elseif (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");
}
}
}
Odpowiedzi:
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(); } }
źródło
GC.Collect()
. Nie ma nic do zebrania. Miałoby to sens, gdybyGC.KeepAlive(t)
nazwano go poConsole.ReadLine();
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.
źródło
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.
źródło
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(); } } }
źródło
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 (; ; ) { } }
źródło
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. }
źródło
Możesz także utworzyć własny (jeśli nie jesteś zadowolony z dostępnych opcji).
Tworzenie własnej
Timer
implementacji 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(); } }
źródło
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(); } }
źródło
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); }
źródło
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;
zvar 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ń
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
źródło
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"); } } }
źródło