如何将计时器添加到C#控制台应用程序


132

就是这样-您如何向C#控制台应用程序添加计时器?如果您可以提供一些示例代码,那将是很好的。


16
注意:这里的答案有一个错误,Timer对象将收集垃圾。对计时器的引用必须存储在静态变量中,以确保其不断变化。
Hans Passant 2014年

@HansPassant您似乎错过了我的回答中的明确声明:“如果您正在开发Windows服务,并且需要定期运行计时器,还建议始终使用静态(在VB.NET中共享)System.Threading.Timer。 ,这样可以避免对计时器对象进行过早的垃圾回收。” 如果人们想复制一个随机示例并盲目使用它,那就是他们的问题。
灰烬

Answers:


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
Khalid,这非常有帮助。谢谢。console.readline()和GC.Collect正是我所需要的。
塞斯·斯皮尔曼

9
@Ralph Willgoss,为什么选择GC.Collect(); 是必须的?
Puchacz

2
@Puchacz我看不出要点GC.Collect()。没有什么可收集的。如果GC.KeepAlive(t)有人称呼它,那将是有道理的Console.ReadLine();
newprint 2015年

1
它在第一次回调后终止
BadPiggie

1
@Khalid Al Hajami“但是,使用for循环永久执行某些功能的样式需要占用大量设备资源,相反,我们可以使用垃圾收集器来执行类似的操作。” 这是绝对荒谬的垃圾。垃圾收集器完全不相关。您是否从书中复制了此书,但不了解所复制的内容?
灰烬

65

使用System.Threading.Timer类。

System.Windows.Forms.Timer主要设计用于通常在Windows Forms UI线程的单个线程中使用。

在.NET框架的开发初期,还添加了一个System.Timers类。但是,通常建议改用System.Threading.Timer类,因为无论如何这只是System.Threading.Timer的包装。

如果您正在开发Windows服务并且需要计时器定期运行,则还建议始终使用静态(在VB.NET中共享)System.Threading.Timer。这样可以避免定时器对象的垃圾过早收集。

这是控制台应用程序中计时器的示例:

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

摘自杰夫·里希特(Jeff Richter)的《CLR Via C#》。顺便提一下,本书强烈建议您在第23章中介绍这3种计时器背后的原理。


您能否提供更多有关实际编码的信息?
约翰·布雷斯勒


埃里克(Eric),我还没有尝试过,但是如果有问题的话,这并不少见。我注意到它也在尝试进行某种线程间同步,这始终是一个棘手的问题。如果可以在设计中避免使用它,这样做总是很明智的。
灰烬

1
Ash-我绝对同意msdn示例。不过,我不会立即取消同步代码,如果timmer在其自己的线程中运行,那么您正在编写多线程应用程序,需要注意与同步有关的问题。
埃里克·塔特尔曼

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

编辑:将硬自旋循环添加到代码中永远不是一个好主意,因为它们会消耗CPU周期而没有任何收益。在这种情况下,添加该循环只是为了阻止应用程序关闭,从而可以观察线程的操作。但是为了正确起见并减少CPU使用率,在该循环中添加了一个简单的Sleep调用。


7
for(;;){}导致100%的CPU使用率。
塞斯·斯皮尔曼

1
如果您有一个无限的for循环,那不是很明显,那么这将导致100%的CPU。要解决此问题,您需要做的就是在循环中添加一个sleep调用。
veight

3
令人惊讶的是,有多少人对for循环是否应该为while循环以及为什么CPU达到100%感兴趣。谈论错过树木的树木!方位角,我个人想知道while(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
最好的解决方案,真的!
Dmitry Ledentsov

8
非常难以理解且违反最佳做法。它看起来很棒,但是不应该在生产中使用,因为某些ppl会自己变成wtf和poo。
Piotr Kula 2013年

2
Reactive Extensions(Rx)已有两年没有积极开发。另外,这些示例没有上下文并且令人困惑。几乎不知道图表或流程示例。
James Bailey 2016年

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

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

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 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.