запускать каждую минуту синхронно с системными часами (не работает в Windows Server 2003)

Я пытаюсь запустить таймер каждую минуту синхронно с системными часами (00:01:00, 00:02:00, 00:03:00 и т. Д.). Это мой код.

private System.Timers.Timer timer;

public frmMain()
{
    timer = new System.Timers.Timer();
    timer.AutoReset = false;
    timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    timer.Interval = GetInterval();
    timer.Start();
}

private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{

        System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString("hh:mm:ss tt"));
            timer.Interval = GetInterval();
            timer.Start();

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

Он отлично работает на моем домашнем ПК.

12:12:00 AM
12:13:00 AM
12:14:00 AM
12:15:00 AM
12:16:00 AM
12:17:00 AM
12:18:00 AM
12:19:00 AM
12:20:00 AM
12:21:00 AM

Однако я получаю странные результаты на своем VPS (Windows Server 2003).

12:11:59 AM
12:12:59 AM
12:13:00 AM
12:13:59 AM
12:14:00 AM
12:14:59 AM
12:15:00 AM
12:15:59 AM
12:16:00 AM
12:16:59 AM
12:17:00 AM
12:17:59 AM
12:18:00 AM
12:18:59 AM
12:19:00 AM
12:19:59 AM
12:20:00 AM
12:20:59 AM
12:21:00 AM

Это потому, что System.Timers.Timer не работает на сервере Windows 2003? Или это проблема с моим VPS?


person user1437139    schedule 15.06.2012    source источник
comment
Никакой таймер не будет абсолютно точным.   -  person SLaks    schedule 15.06.2012
comment
Я знаю, но мой таймер работает с точностью до миллисекунд. Он отлично работает на обычных ПК.   -  person user1437139    schedule 15.06.2012
comment
См. Stack Overflow не требует ваших навыков SEO.   -  person John Saunders    schedule 16.06.2012
comment
возможный дубликат C #, System.Timers .Timer запускается каждые 15 минут синхронно с системными часами одним и тем же пользователем.   -  person Henk Holterman    schedule 23.06.2012


Ответы (5)


Вместо использования DateTime.Now и извлечения отдельных частей просто используйте Клещи. Получите отметки при запуске, а затем вычислите, какими должны быть отметки для следующего такта таймера. Как только произойдет этот тик таймера, используйте последнее значение, чтобы вычислить, каким должно быть следующее значение.

Пример:

    private const long MILLISECOND_IN_MINUTE = 60 * 1000;
    private const long TICKS_IN_MILLISECOND = 10000;
    private const long TICKS_IN_MINUTE = MILLISECOND_IN_MINUTE * TICKS_IN_MILLISECOND;

    private System.Timers.Timer timer;
    private long nextIntervalTick;

    public void frmMain()
    {
        timer = new System.Timers.Timer();
        timer.AutoReset = false;
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.Interval = GetInitialInterval();
        timer.Start();
    }

    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

        System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString("hh:mm:ss tt"));
        timer.Interval = GetInterval();
        timer.Start();

    }
    private double GetInitialInterval()
    {
        DateTime now = DateTime.Now;
        double timeToNextMin = ((60 - now.Second) * 1000 - now.Millisecond) + 15;
        nextIntervalTick = now.Ticks + ((long)timeToNextMin * TICKS_IN_MILLISECOND);

        return timeToNextMin;
    }
    private double GetInterval()
    {
        nextIntervalTick += TICKS_IN_MINUTE;
        return TicksToMs(nextIntervalTick - DateTime.Now.Ticks);
    }
    private double TicksToMs(long ticks)
    {
        return (double)(ticks / TICKS_IN_MILLISECOND);
    }

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

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

person Trisped    schedule 15.06.2012
comment
Спасибо! Я пробовал запустить его. Но что-то не так с GetInterval (). Он возвращает 0. Кроме того, не будет ли эта строка nextIntervalTick + = TICKS_IN_MINUTE; сделать nextIntervalTick в конечном итоге переполненным? - person user1437139; 15.06.2012
comment
@ user1437139 К сожалению, TicksToMs следовало разделить на TICKS_IN_MILLISECOND вместо TICKS_IN_MINUTE. Код обновлен и исправлен. - person Trisped; 16.06.2012
comment
Он отлично работает на моем ПК. [9:30:00, 9:31:00, 9:32:00, 9:33:00, 9:34:00, 9:35:00] Но мне все еще становится плохо результаты на моем VPS (с погрешностью в одну секунду). [9:29:59, 9:30:59, 9:31:59, 9:32:59, 9:33:59, 9:34:59] Я думаю, что нужно что-то делать с GetInitialInterval () - person user1437139; 16.06.2012
comment
@ user1437139 Добавьте 15 к timeToNextMin в GetInitialInterval (пример обновлен). А еще лучше измените DateTime.Now.ToString("hh:mm:ss tt") на DateTime.Now.ToString("hh:mm:ss.fffffff tt") и добавьте количество миллисекунд, необходимое для приведения его к правильному времени. Итак, если 12: 01.9915, тогда добавьте 9 к timeToNextMin - person Trisped; 16.06.2012
comment
Спасибо за помощь Trisped :) Очень признателен. Мне нужно добавить около 20 мс. Есть идеи, почему на моем VPS таймер работает быстрее, чем на моем ПК? - person user1437139; 16.06.2012
comment
Кстати, этот таймер начнет сдвигаться, скажем, через несколько недель или месяцев? - person user1437139; 16.06.2012
comment
Господи ... Все это содержится в моем первом ответе! Вы каждый раз сбрасываете интервал, поэтому дрейф не накапливается. Как вы не удосужились прочитать все ответы? - person Totero; 16.06.2012
comment
@ user1437139 Существует разница в точности часов: одни часы имеют точность, кратную 1000, а другие нет. Я ожидаю, что дрейф станет проблемой. Вы можете либо пересчитывать ежедневно (сравнивать вновь рассчитанное время с тем, что было бы использовано GetInterval, чтобы предотвратить проблему двойного срабатывания), либо изменить nextIntervalTick на DateTime и добавить минуту для каждого цикла. Хитрость заключается в том, чтобы отследить, в какое время цикл был последний раз запущен, и добавить к нему минуту (хотя другие варианты будут работать в зависимости от ситуации). - person Trisped; 16.06.2012
comment
@Totero: да, вы прокомментировали проблему с точностью таймера, но это только часть проблемы. Более серьезная проблема заключалась в том, что не было гарантии, что таймер сработает по крайней мере через 59 секунд, поэтому он получит два события с интервалом в секунду друг от друга. - person Trisped; 16.06.2012

((60 - сейчас. Секунда) * 1000 - сейчас. Миллисекунда)

Это означает, что если now.Second окажется равным 59, ваше время снова сработает менее чем через секунду. Это причина ваших странных результатов (таймер не срабатывает при смещении ровно 0 секунд).

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

person 500 - Internal Server Error    schedule 15.06.2012
comment
Я не вижу проблемы с ((60 - сейчас.Секунда) * 1000 - сейчас.Миллисекунда). Если таймер инициализируется, когда now.Second = 59, таймер сработает менее чем через секунду. К тому времени, когда он сработает, now.Second будет 00. Новый интервал таймера будет рассчитан, и он снова сработает точно в 00 (через 60 секунд). - person user1437139; 15.06.2012

Обычные таймеры, такие как System.Timers.Timer, неточны и недостаточно хороши для достижения интервала в 1 мс.

Во-первых, у них внутренняя частота обновления 10-15 мсек. Во-вторых, в зависимости от системы другие потоки могут работать на ~ 15 мсек, задерживая ваш таймер до того, как Windows заставит их уступить.

Если вам нужна более высокая точность, чем таймер, используйте System.Diagnostics.Stopwatch, как указано в другом потоке, оно может быть от 0,3 мс и интегрировано с вашей средой .NET.

Другой вариант - использовать время мультимедиа (с точностью до 1 мс).

В любом случае здесь - отличное руководство по этой проблеме.

Разбивая это:

Дрейф таймера обычно увеличивает задержку таймера. Но вы видите, что происходит обратное. Поскольку таймеры не имеют точности до миллисекунд (они имеют точность только в диапазоне 15 мс), они часто будут запускаться с такой степенью детализации. Таким образом, в некоторых случаях таймер срабатывает за несколько миллисекунд до минутной отметки (что приводит к срабатыванию сразу же после этого). Если вам нужно, чтобы он запускался только в новую минуту, я бы добавил несколько миллисекунд времени ожидания для компенсации (5 мс должны сделать это).

Ваш домашний компьютер не так быстр (что означает, что он демонстрирует дополнительный сдвиг таймера, связанный с обработчиком таймера) и обычно запускает событие в следующую секунду. Вашему рабочему компьютеру иногда удается обработать событие таймера достаточно быстро, чтобы он записывал прошедшие 59 секунд (что, я считаю, усечено и, вероятно, 59.900 ~ 59.999). Это также может произойти, если машина является многослойной, поскольку нет задержки выхода нити и таймер может срабатывать очень быстро.

Это причина неполадок вашего таймера.

person Totero    schedule 15.06.2012
comment
Мне не нужна точность в 1 мс. Полсекунды вполне достаточно. У меня вопрос: почему я получаю странные результаты на сервере Windows 2003? - person user1437139; 15.06.2012
comment
Stopwatch не подходит для этого. Это измеряет только время. OP хочет запускать код каждый раз по истечении определенного интервала. - person Paul Phillips; 15.06.2012
comment
@PaulPhillips, а как насчет BackgroundWorker в цикле while, который проверяет секундомер, и если пришло время выполнить, вызовите метод ReportProgress (0) для обновления графического интерфейса - person Cole Johnson; 15.06.2012
comment
OK. Я объяснил дальше. - person Totero; 15.06.2012

Привет, еще один пример - использовать таймер из System.Windows.Threading.

using System;
using System.Windows.Threading;

namespace Yournamespace
{
    public partial class TestTimer
    {
        DispatcherTimer dispatcherTimer1m;
        public TestTimer()
        {
            dispatcherTimer1m = new  DispatcherTimer();
            dispatcherTimer1m.Tick += new EventHandler(DispatcherTimer1m_Tick);
            dispatcherTimer1m.Interval = TaskHelper.GetSyncIntervalms;
            dispatcherTimerm.Start();

        }

        private void DispatcherTimer1m_Tick(object sender, EventArgs e)
        {
            try
            {
            dispatcherTimer1m.Stop();           
            //Do your effort here
        }
        catch (Exception exc)
        {
                //Your exception handled here
        }
        finally
        {  
            dispatcherTimer1m.Interval = TaskHelper.GetSyncInterval1m;
            dispatcherTimer1m.Start();
            }
        }   
    }
    public class TaskHelper
    {
        private const ushort internalUpdate = 15;//ms        
        public static TimeSpan GetSyncInterval1m => new TimeSpan(0, 0, 0, 60,internalUpdate).Subtract( new TimeSpan(0, 0, 0, DateTime.Now.Second, 0));
    }
}
person luka    schedule 31.07.2019

Помните, что Windows Server по умолчанию настроен для совместного использования ресурсов с фоновыми задачами более охотно, чем клиентские версии, поэтому точность таймера может быть снижена, если сервер выполняет несколько фоновых задач.

Вы можете попробовать временно изменить его, чтобы установить приоритет задачи переднего плана, чтобы увидеть, дает ли это разные результаты - настройка находится где-то на панели управления системой, вам нужны два переключателя, одна с надписью «Программы», а другая с надписью «Фон» услуги »или аналогичный.

person Graham Wager    schedule 15.06.2012