.NET,事件每分钟(分钟)。计时器是最好的选择吗?

时间:2009-08-25 17:56:09

标签: c# timer system.reactive

我希望使用c#在Windows窗体应用程序中按分钟(按时钟)每分钟做一些事情。我只是想知道最好的方法是什么?

我可以使用计时器并将其间隔设置为60000,但要让它在一分钟内运行,我必须准确地启用它,而不是真正可行。

我可以使用计时器并将其间隔设置为1000.然后在其tick事件中,我可以根据我设置的变量检查当前时钟,如果分钟已经更改,则运行我的代码。这让我很担心,因为我让我的电脑每1秒做一次检查,以便每1分钟进行一次工作。当然这很难看?

我正在使用Windows窗体和.Net 2.0,因此不想使用.Net 3.5附带的DispatchTimer

这一定是一个相当普遍的问题。你有没有更好的方法来做到这一点?

15 个答案:

答案 0 :(得分:47)

以aquinas的答案为基础,这种答案可以漂移,并且不会在一分钟内在一分钟内准确地打勾:

static System.Timers.Timer t;

static void Main(string[] args)
{
    t = new System.Timers.Timer();
    t.AutoReset = false;
    t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
    t.Interval = GetInterval();
    t.Start();
    Console.ReadLine();
}

static double GetInterval()
{
    DateTime now = DateTime.Now;
    return ((60 - now.Second) * 1000 - now.Millisecond);
}

static void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    Console.WriteLine(DateTime.Now.ToString("o"));
    t.Interval = GetInterval();
    t.Start();
}

在我的方框中,此代码在每分钟的.02秒内始终保持一致:

2010-01-15T16:42:00.0040001-05:00
2010-01-15T16:43:00.0014318-05:00
2010-01-15T16:44:00.0128643-05:00
2010-01-15T16:45:00.0132961-05:00

答案 1 :(得分:28)

怎么样:

int startin = 60 - DateTime.Now.Second;
var t = new System.Threading.Timer(o => Console.WriteLine("Hello"), 
     null, startin * 1000, 60000);

答案 2 :(得分:8)

创建一个每1秒触发一次的Timer控件(通常只做一个简单的检查),会给你的应用程序增加可忽略的开销。

只需将Environment.TickCountDateTime.Now的值与上次存储的时间(之前的“分钟刻度”)进行比较,您就应该有一个相当精确的解决方案。这两个时间值的分辨率大约为15毫秒,应该足以满足您的需要。

请注意,Timer控件的间隔不能保证是精确的,甚至是现在的任何地方,因为它在Windows消息循环上运行,与之相关联用户界面的响应能力。永远不要依赖它来进行适度精确的计时 - 尽管它可以用于触发重复事件,你可以使用更敏感的方法来检查时间,例如上面给出的两种方法之一。

答案 3 :(得分:7)

你可以通过反应式扩展来解决这个问题,这将为你解决许多与计时器相关的问题(时钟更改,应用程序休眠等)。使用Nuget包Rx-Main和代码如下:

Action work = () => Console.WriteLine(DateTime.Now.ToLongTimeString());

Scheduler.Default.Schedule(
    // start in so many seconds
    TimeSpan.FromSeconds(60 - DateTime.Now.Second), 
    // then run every minute
    () => Scheduler.Default.SchedulePeriodic(TimeSpan.FromMinutes(1), work));               

Console.WriteLine("Press return.");
Console.ReadLine();

请阅读此处(搜索“ISchedulerPeriodic简介”)以查看此问题所涉及的所有问题:http://blogs.msdn.com/b/rxteam/archive/2012/06/20/reactive-extensions-v2-0-release-candidate-available-now.aspx

答案 4 :(得分:5)

我jsut使用WPF DispatcherTimer编写了这个类,但您可以将调度程序交换为支持从睡眠状态唤醒时更改的任何计时器。

该类由固定时间步长构成,并支持启动/停止/复位,启动/停止/启动就像恢复操作一样。在这方面,计时器就像一个秒表。

时钟实现只是创建一个间隔为1秒的类并监听事件。虽然这是一个实时时钟,但要小心,如果tick事件花费的时间超过完成时间间隔,你会注意到时钟将尝试赶上实时,这将导致一连串的tick事件被引发。

public class FixedStepDispatcherTimer
{
    /// <summary>
    /// Occurs when the timer interval has elapsed.
    /// </summary>
    public event EventHandler Tick;

    DispatcherTimer timer;

    public bool IsRunning { get { return timer.IsEnabled; } }

    long step, nextTick, n;

    public TimeSpan Elapsed { get { return new TimeSpan(n * step); } }

    public FixedStepDispatcherTimer(TimeSpan interval)
    {
        if (interval < TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException("interval");
        }
        this.timer = new DispatcherTimer();
        this.timer.Tick += new EventHandler(OnTimerTick);
        this.step = interval.Ticks;
    }

    TimeSpan GetTimerInterval()
    {
        var interval = nextTick - DateTime.Now.Ticks;
        if (interval > 0)
        {
            return new TimeSpan(interval);
        }
        return TimeSpan.Zero; // yield
    }

    void OnTimerTick(object sender, EventArgs e)
    {
        if (DateTime.Now.Ticks >= nextTick)
        {
            n++;
            if (Tick != null)
            {
                Tick(this, EventArgs.Empty);
            }
            nextTick += step;
        }
        var interval = GetTimerInterval();
        Trace.WriteLine(interval);
        timer.Interval = interval;
    }

    public void Reset()
    {
        n = 0;
        nextTick = 0;
    }

    public void Start()
    {
        var now = DateTime.Now.Ticks;
        nextTick = now + (step - (nextTick % step));
        timer.Interval = GetTimerInterval();
        timer.Start();
    }

    public void Stop()
    {
        timer.Stop();
        nextTick = DateTime.Now.Ticks % step;
    }
}

答案 5 :(得分:3)

创建方法或将此代码放在您希望计时器启动的位置:

 int time = 60 - DateTime.Now.Second; // Gets seconds to next minute
        refreshTimer.Interval = time * 1000;
        refreshTimer.Start();

然后在您的tick事件中将间隔设置为60000:

  private void refreshTimer_Tick(object sender, EventArgs e)
    {
        refreshTimer.Interval = 60000; // Sets interval to 60 seconds
        // Insert Refresh logic
    }

答案 6 :(得分:2)

运行一些代码以查看分钟是否每秒更改一次不需要太多的CPU时间,并且应该可以接受。

答案 7 :(得分:2)

Quartz.NET怎么样?我认为这是一个做定时行动的好框架。

答案 8 :(得分:2)

通过使用ReactiveExtensions,如果您对打印到控制台这样简单的事情感兴趣,可以使用以下代码。

using System;
using System.Reactive.Linq;
namespace ConsoleApplicationExample
{
    class Program
    {
        static void Main()
        {
            Observable.Interval(TimeSpan.FromMinutes(1))
            .Subscribe(_ =>
            {                   
                Console.WriteLine(DateTime.Now.ToString());
            });
            Console.WriteLine(DateTime.Now.ToString()); 
            Console.ReadLine();
        }
    }
}

答案 9 :(得分:1)

您可以设置两个计时器。一个初始的短间隔计时器(可能每秒触发一次,但取决于第二个计时器必须按分钟计算的次数)。

您只能触发短间隔定时器,直到达到主间隔定时器的所需开始时间。一旦达到初始时间,就可以激活第二个主间隔定时器,并且可以停用短间隔定时器。

void StartTimer()
{

  shortIntervalTimer.Interval = 1000;
  mainIntervalTimer.Interval = 60000; 

  shortIntervalTimer.Tick += 
    new System.EventHandler(this.shortIntervalTimer_Tick);
  mainIntervalTimer.Tick += 
    new System.EventHandler(mainIntervalTimer_Tick);

  shortIntervalTimer.Start();

}

private void shortIntervalTimer_Tick(object sender, System.EventArgs e)
{
  if (DateTime.Now.Second == 0)
    {
      mainIntervalTimer.Start();
      shortIntervalTimer.Stop();
    }
}

private void mainIntervalTimer_Tick(object sender, System.EventArgs e)
{
  // do what you need here //
}

答案 10 :(得分:1)

或者,你可以睡觉暂停执行,直到它超时,这应该接近你想要的时间。这只会在睡眠结束时唤醒计算机,这样可以节省CPU时间,让CPU在处理事件之间断电。

这样做的好处是可以修改超时,使其不会漂移。

int timeout = 0;

while (true)  {
  timeout = (60 - DateTime.Now.Seconds) * 1000 - DateTime.Now.Millisecond;
  Thread.Sleep(timeout);

  // do your stuff here
}

答案 11 :(得分:0)

使用定时器设置每秒运行一次(或毫秒,无论您的准确度阈值是多少),然后编写方法以运行您的功能当且仅当当前时间超过“在分钟”点之前的阈值时

答案 12 :(得分:0)

我正在使用的计划任务是System.Threading.Timer(System.Threading.TimerCallback,object,int,int),回调设置为我想要执行的代码,基于提供的时间间隔周期值的毫秒数。

答案 13 :(得分:0)

如何将aquinas的答案和'民意调查'结合起来:(对语言混合道歉)

def waitForNearlyAMinute:
    secsNow = DateTime.Now.Second;
    waitFor = 55 - secsNow;
    setupTimer(waitFor, pollForMinuteEdge)

def pollForMinuteEdge:
    if (DateTime.Now.Second == 0):
        print "Hello, World!";
        waitForNearlyAMinute();
    else:
        setupTimer(0.5, pollForMinuteEdge)

答案 14 :(得分:-1)

我有一个基于 Environment.TickCount 的解决方案

    static void Main(string[] args)
    {
        //constatnt total miliseconds to one minute
        const Int32 minuteMilisecond = 60 * 1000;

        //get actual datetime
        DateTime actualDateTime = DateTime.UtcNow;

        //compenzation to one minute
        Int32 nexTimer = Environment.TickCount + ((59 - actualDateTime.Second) * 1000) + (999 - actualDateTime.Millisecond);

        //random fuction to simulate different delays on thread
        Random rnd = new Random();

        //main loop
        while (true) 
        {
            if (Environment.TickCount > nexTimer)
            {
                nexTimer += minuteMilisecond;

                //execute your code here every minute
                Console.WriteLine($"actual DateTime: {DateTime.Now.ToString("yyyy.MM.dd HH:mm:ss:ffff")}");
            }

            //random sleep between 100 - 200 ms
            Thread.Sleep(rnd.Next(100, 200));
        }
    }