Вызов System.Threading.Timer длится пару секунд каждый день - PullRequest
4 голосов
/ 07 июня 2011

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

TimeSpan runTime = new TimeSpan(2, 0, 0); // 2 AM
TimeSpan timeToFirstRun = runTime - DateTime.Now.TimeOfDay;

if (timeToFirstRun.TotalHours < 0)
{
    timeToFirstRun += TimeSpan.FromDays(1.0);
}

_dailyNodalRunTimer = new Timer(
    RunNodalDailyBatch,
    null,
    timeToFirstRun,
    TimeSpan.FromDays(1.0)); //repeat event daily

Этот код инициализации вызывается один раз при первом запуске службы, за последние несколько дней я вошел в систему при срабатывании таймера:

2011-05-21 02:00:01.580
2011-05-22 02:00:03.840
...
2011-05-31 02:00:25.227
2011-06-01 02:00:27.423
2011-06-02 02:00:29.847

Как вы можете видеть, как он дрейфует на 2 секунды каждый день, все дальше и дальше от того момента, когда он должен был выстрелить (в 2 часа ночи).

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

EDIT

Я попытался использовать System.Timers.Timer, и, похоже, проблема та же. Сброс интервала происходит потому, что вы не можете запланировать начальное время до первого тика в System.Timers.Timer, как в System.Threading.Timer

.
int secondsInterval = 5;

double secondsUntilRunFirstRun = secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval);
var timer = new System.Timers.Timer(secondsUntilRunFirstRun * 1000.0);
timer.AutoReset = true;
timer.Elapsed += (sender, e) =>
    {
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff"));

        if (timer.Interval != (secondsInterval * 1000.0))
            timer.Interval = secondsInterval * 1000.0;
    };
timer.Start();

Произведите следующие моменты, вы можете увидеть, как они слегка дрейфуют:

06:47:40.020
06:47:45.035
06:47:50.051
...
06:49:40.215
06:49:45.223
06:49:50.232

Так что я полагаю, что лучший подход - это просто перенести таймер в обработчик тиков? Следующее производит тик с регулярным интервалом в течение ~ 15 миллисекунд

double secondsUntilRunFirstRun = secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval);

var timer = new System.Timers.Timer(secondsUntilRunFirstRun * 1000.0);
timer.AutoReset = false;
timer.Elapsed += (sender, e) =>
{
    Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff"));

    timer.Interval = (secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval)) * 1000.0;
};
timer.Start();


06:51:45.009
06:51:50.001
...
06:52:50.011
06:52:55.013
06:53:00.001

Ответы [ 5 ]

4 голосов
/ 07 июня 2011

Не допускайте накопления погрешностей таймера.Используйте RTC, чтобы вычислить, сколько мс осталось до времени ожидания.Sleep / setInterval до половины этого времени.Когда таймер сработает / вернется в спящий режим, снова используйте RTC, чтобы пересчитать оставшийся интервал, и снова установите интервал / спящий режим в период полураспада.Повторяйте этот цикл, пока оставшийся интервал не станет меньше 50 мс.Затем ЦП зацикливается на RTC, пока не будет превышено желаемое время.Запустите событие.

Ргдс, Мартин

3 голосов
/ 07 июня 2011

Ни один из таймеров в .NET Framework не будет точным. В игре слишком много переменных. Если вы хотите более точный таймер, взгляните на мультимедийные таймеры . Я никогда не использовал их в течение более длительных периодов времени, но я подозреваю, что они все еще существенно более точны, чем таймеры BCL.

Но я не вижу причин, которые бы запретили вам использовать класс System.Threading.Timer. Вместо указания TimeSpan.FromDays(1) используйте Timeout.Infinite для предотвращения периодической сигнализации. Затем вам придется перезапустить таймер, но вы можете указать 23:59:58 или 1.00: 00: 05 для параметра dueTime в зависимости от того, что вы рассчитываете в следующий раз, когда будет сигнал в 2:00.

Кстати, System.Timers.Timer будет работать не лучше, чем System.Threading.Timer. Причина в том, что первый на самом деле использует последний за кулисами в любом случае. System.Timers.Timer просто добавляет несколько удобных функций, таких как автоматический сброс и маршалинг выполнения Elapsed в размещенном потоке ISynchronizeInvoke (обычно в потоке пользовательского интерфейса).

3 голосов
/ 07 июня 2011

Я думаю, что вы уже поняли это, но если вы хотите, чтобы что-то сработало в определенное время дня (2 часа ночи), вам было бы лучше с выделенной нитью, которая спит, периодически просыпается и смотрит, не пришло ли время бежать еще. Было бы уместно спать около 100 миллисекунд, при этом процессор практически не работал бы.

Другой подход заключается в том, что после того, как вы выполнили свою ежедневную работу, вы вычисляете, когда наступать следующий пожар, исходя из 2:00 завтрашнего дня - DateTime.Current и т. Д. Это может все же быть не так точно, как вы хотите (я не уверен ) но по крайней мере дрейф не станет хуже, хуже и хуже.

2 голосов
/ 07 июня 2011

Если вам нужно точное время, вам понадобится System.Timers.Timer class.

Также смотрите этот вопрос: .NET, событие каждую минуту (по минутам). Таймер - лучший вариант?

1 голос
/ 07 июня 2011

С msdn :

System.Threading.Timer - это простой, легкий таймер ... Для серверных функций таймера вы можете рассмотреть возможность использования System.Timers.Timer , который вызывает события и имеет дополнительные функции.

Вы также можете переместить его в Планировщик задач Windows

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...