.NET, событие каждую минуту (по минутам). Является ли таймер лучшим вариантом? - PullRequest
39 голосов
/ 25 августа 2009

Я хочу делать вещи каждую минуту по минутам (по часам) в приложении Windows Forms, используя c #. Мне просто интересно, как лучше это сделать?

Я мог бы использовать таймер и установить его интервал равным 60000, но чтобы он работал в минуту, мне нужно было бы включить его точно в минуту, но это не реально.

Я мог бы использовать таймер и установить его интервал равным 1000. Затем, в пределах его тикового события, я мог бы проверить текущую минуту часов по переменной, которую я установил, если минута изменилась, запустите мой код. Это беспокоит меня, потому что я заставляю свой компьютер проверять каждую 1 секунду, чтобы выполнять работу каждые 1 минуту. Неужели это безобразно?

Я использую формы Windows и .Net 2.0, поэтому не хочу использовать DispatchTimer, который поставляется с .Net 3.5

Это должно быть довольно распространенная проблема. У кого-нибудь из вас есть лучший способ сделать это?

Ответы [ 14 ]

49 голосов
/ 16 января 2010

Опираясь на ответ от Аквинских островов, который может дрейфовать и который не тикает точно в минуту, в течение одной секунды минуты:

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

На моем ящике этот код постоянно помечается в течение 0,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
29 голосов
/ 25 августа 2009

Как насчет:

int startin = 60 - DateTime.Now.Second;
var t = new System.Threading.Timer(o => Console.WriteLine("Hello"), 
     null, startin * 1000, 60000);
8 голосов
/ 25 августа 2009

Создание элемента управления Timer, который срабатывает каждую 1 секунду (и обычно не делает ничего, кроме простой проверки), добавит незначительные накладные расходы к вашему приложению.

Просто сравните значение Environment.TickCount или DateTime.Now с последним сохраненным временем (предыдущий «минутный тик»), и вы получите достаточно точное решение. Разрешение этих двух значений времени составляет около 15 мс, что должно быть достаточно для ваших целей.

Обратите внимание, однако, что интервал Timer элемента управления не гарантирован , чтобы быть таким точным или даже где-либо еще, поскольку он работает в цикле сообщений Windows, который связан с отзывчивостью пользовательский интерфейс. Никогда не полагайтесь на это даже для умеренно точного времени - хотя это достаточно для запуска повторяющихся событий, где вы можете проверить время, используя более чувствительный метод , такой как один из двух приведенных выше.

7 голосов
/ 30 мая 2013

Вы можете прибить это к реактивным расширениям, которые позаботятся о многих проблемах, связанных с таймером (смена часов, спящий режим приложения и т. Д.). Используйте пакет 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

5 голосов
/ 15 июля 2010

Я написал этот класс, используя WPF DispatcherTimer, но вы можете поменять диспетчер на любой таймер, который поддерживает изменение, когда он выходит из спящего режима.

Класс построен с фиксированным временным шагом и поддерживает запуск / остановку / сброс, запуск / остановку / запуск работает как операция возобновления. В этом отношении таймер похож на секундомер.

Реализация часов просто создала бы класс с интервалом в 1 секунду и прослушала событие. Однако будьте осторожны с тем, что это часы реального времени, если тиковое событие занимает больше времени, чем интервал, чтобы завершить, вы заметите, что часы попытаются догнать реальное время, что вызовет всплеск тиковых событий.

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;
    }
}
3 голосов
/ 20 мая 2014

Создайте метод или поместите этот код туда, где вы хотите запустить таймер:

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

А затем на вашем тиковом событии установите интервал 60000:

  private void refreshTimer_Tick(object sender, EventArgs e)
    {
        refreshTimer.Interval = 60000; // Sets interval to 60 seconds
        // Insert Refresh logic
    }
2 голосов
/ 27 мая 2015

Используя 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();
        }
    }
}
2 голосов
/ 03 сентября 2009

А как насчет Quartz.NET ? Я думаю, это хорошая основа для выполнения запланированных действий.

2 голосов
/ 25 августа 2009

Выполнение небольшого количества кода, чтобы увидеть, изменилась ли минута один раз в секунду, не должно требовать много процессорного времени и должно быть приемлемым.

1 голос
/ 04 сентября 2009

Кроме того, вы можете спать, чтобы приостановить выполнение до истечения времени ожидания, которое должно быть близко к желаемому времени. Это разбудит компьютер только после завершения сна, так что сэкономит ваше процессорное время и снизит нагрузку на процессор между событиями обработки.

Преимущество заключается в изменении времени ожидания, чтобы оно не смещалось.

int timeout = 0;

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

  // do your stuff here
}
...