Я использую 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 мс) и в моем коде нет ошибок.
Кто-нибудь знает, почему это происходит и как это исправить? Любые предложения приветствуются. Спасибо!
Также: Какого порядка точности вы ожидаете? На самом деле, это около 17 мс, если я правильно помню. По крайней мере, в двухзначных числах даже в последней системе.
Ясно, что интервал между предыдущим и следующим экземпляром еще не 5 секунд" - я бы не был настолько уверен, что это так однозначно (см. предыдущие комментарии). Также я бы порекомендовал немного углубиться в документацию - «Поскольку класс Timer зависит от системных часов, он имеет то же разрешение, что и системные часы. При запуске в системе Windows, системные часы которой имеют разрешение приблизительно 15 миллисекунд».
Таймер может быть недостаточно точным для ваших нужд. Что вы пытаетесь сделать, что требует такого точного времени?
Пожалуйста, обратите внимание на методы RoundUp и RoundDown, 10:10:25.001 округлит до 10:10:25, 10:10:29.999 также округлит до 10:10:25. Мне не нужна абсолютно точная синхронизация, но мне нужно, чтобы таймер не срабатывал меньше интервала и не более чем в два раза больше интервала, чтобы функция RoundDown работала корректно.
Я буду с вами честен: если разница в четвертом десятичном интервале таймера вас не устраивает, то ваш алгоритм абсолютно ужасен и определенно не подходит для системы с защищенным режимом, такой как Windows. Просто напишите лучший алгоритм. В противном случае используйте ОС реального времени, например QNX, Arduino или что-то в этом роде.





Предполагая, что вы использовали 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 внезапно увеличится во время срабатывания события, это приведет к отклонению расчетного интервала.
Решение зависит от того, что вы хотите сделать.
Если вы просто хотите выполнять метод каждые N секунд, вы предполагаете, что таймер всегда работает правильно через заданный интервал.
Если вы хотите выполнить метод N раз в течение определенного периода времени, вы можете записать количество выполнений. Если по истечении указанного времени количества выполнений недостаточно, вы можете добавить еще одно (как и в високосных секундах).
Округляем до ближайшего времени:
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 работала корректно.
Полученное вами время не является временем срабатывания события, и вы не можете точно определить это время, поэтому можете доверять только интервалу таймера. Округлить в меньшую сторону нельзя, но можно до ближайшего времени.
если d.Ticks является целым числом, у вас есть целочисленное деление....