С# Таймер срабатывает до окончания интервала

Я использую System.Timers.Timer в C#, чтобы запланировать задачу, которую необходимо выполнять каждые 5 секунд. Однако я заметил, что таймер иногда срабатывает до окончания интервала. Вот мой код:

internal class IcbDataProcessor : BackgroundService
{
    private readonly ILogger<IcbDataProcessor> _logger;
    private readonly System.Timers.Timer _timer;
    private readonly TimeSpan _interval = TimeSpan.FromSeconds(5);

    public IcbDataProcessor(ILogger<IcbDataProcessor> logger)
    {
        _logger = logger;
        _timer = new System.Timers.Timer(_interval);
        _timer.Elapsed += Timer_Elapsed;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var now = DateTime.Now;
        var nextTime = RoundUp(now, _interval);
        if (nextTime > now)
        {
            await Task.Delay(nextTime - now, stoppingToken);
        }
        _timer.Start();
    }

    private static DateTime? _prevRoundedTime;
    private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
    {
        try
        {
            var roundedTime = RoundDown(DateTime.Now, _interval);
            if (roundedTime == _prevRoundedTime)
            {
                _logger.LogWarning("IcbDataProcessor: Prev Rounded Time is equals to Rounded Time: {Time}", roundedTime);
            }

            //To do
            _prevRoundedTime = roundedTime;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "DataProcessor EXCEPTION {Message}", ex.Message);
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _timer?.Dispose();
        await Task.CompletedTask;
    }

    public static DateTime RoundUp(DateTime date, TimeSpan d)
    {
        return new DateTime((date.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, date.Kind);
    }

    public static DateTime RoundDown(DateTime date, TimeSpan d)
    {
        return new DateTime(date.Ticks / d.Ticks * d.Ticks, date.Kind);
    }
}

Когда я проверяю сохраненное время, я вижу, что бывают случаи, когда таймер запускается следующим образом: 10:10:25.001, 10:10:29.999. Очевидно, что интервал между предыдущим и следующим экземпляром еще не составляет 5 секунд, поэтому мой roundedTime округляется до того же числа. Я проверил и уверен, что мое событие завершается очень быстро (около 20 мс) и в моем коде нет ошибок.

Кто-нибудь знает, почему это происходит и как это исправить? Любые предложения приветствуются. Спасибо!

если d.Ticks является целым числом, у вас есть целочисленное деление....

Mitch Wheat 04.05.2024 06:23

Также: Какого порядка точности вы ожидаете? На самом деле, это около 17 мс, если я правильно помню. По крайней мере, в двухзначных числах даже в последней системе.

Fildor 04.05.2024 09:05

Ясно, что интервал между предыдущим и следующим экземпляром еще не 5 секунд" - я бы не был настолько уверен, что это так однозначно (см. предыдущие комментарии). Также я бы порекомендовал немного углубиться в документацию - «Поскольку класс Timer зависит от системных часов, он имеет то же разрешение, что и системные часы. При запуске в системе Windows, системные часы которой имеют разрешение приблизительно 15 миллисекунд».

Guru Stron 04.05.2024 09:39

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

flackoverstow 04.05.2024 10:42

Пожалуйста, обратите внимание на методы RoundUp и RoundDown, 10:10:25.001 округлит до 10:10:25, 10:10:29.999 также округлит до 10:10:25. Мне не нужна абсолютно точная синхронизация, но мне нужно, чтобы таймер не срабатывал меньше интервала и не более чем в два раза больше интервала, чтобы функция RoundDown работала корректно.

Minh Giang 04.05.2024 16:24

Я буду с вами честен: если разница в четвертом десятичном интервале таймера вас не устраивает, то ваш алгоритм абсолютно ужасен и определенно не подходит для системы с защищенным режимом, такой как Windows. Просто напишите лучший алгоритм. В противном случае используйте ОС реального времени, например QNX, Arduino или что-то в этом роде.

Blindy 04.05.2024 17:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Предполагая, что вы использовали DateTime.Now для проверки времени, как в коде, давайте посмотрим на пример течения времени, например:

Событие Время Интервал Прошедшее Timer_Elapsed уволен 10:10:24.986 0 После нескольких циклов процессора
Получите DateTime.Now 10:10:25.001 я₀ 0+я₀ Timer_Elapsed уволен 10:10:29.996 5.01 После нескольких циклов процессора
Получите DateTime.Now 10:10:29.999 я₁ 5.01+я₁

Интервал между двумя обследованиями составляет 5.01 - (i₀ - i₁). Если значение i внезапно увеличится во время срабатывания события, это приведет к отклонению расчетного интервала.

Решение зависит от того, что вы хотите сделать.

  1. Если вы просто хотите выполнять метод каждые N секунд, вы предполагаете, что таймер всегда работает правильно через заданный интервал.

  2. Если вы хотите выполнить метод N раз в течение определенного периода времени, вы можете записать количество выполнений. Если по истечении указанного времени количества выполнений недостаточно, вы можете добавить еще одно (как и в високосных секундах).

  3. Округляем до ближайшего времени:

    public static DateTime Round(DateTime date, TimeSpan d)
    {
        var t = d.Ticks;
        var dt = date.Ticks;
        var rt = dt / t * t;
        if (dt - rt >= t / 2)
            rt += t;
        return new DateTime(rt, date.Kind);
    }
    

Пожалуйста, обратите внимание на методы RoundUp и RoundDown, 10:10:25.001 округлит до 10:10:25, 10:10:29.999 также округлит до 10:10:25. Мне не нужна абсолютно точная синхронизация, но мне нужно, чтобы таймер не срабатывал меньше интервала и не более чем в два раза больше интервала, чтобы функция RoundDown работала корректно.

Minh Giang 04.05.2024 16:25

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

shingo 04.05.2024 17:10

Другие вопросы по теме