我在 C# 中使用 System.Timers.Timer 来安排需要每 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 毫秒)并且我的代码中没有错误。
有谁知道为什么会发生这种情况以及如何解决它?任何建议表示赞赏。谢谢!
假设您像代码中一样使用
DateTime.Now
来检查时间,让我们看一个时间流逝的示例,如下所示:
活动 | 间隔 | 已过去 |
---|---|---|
被解雇了 |
0 | |
经过一些 CPU 周期后 获取
|
我₀ | 0+i₀ |
被解雇了 |
5.01 | |
经过一些 CPU 周期后 获取
|
我₁ | 5.01+i₁ |
两次检查之间的间隔是
5.01 - (i₀ - i₁)
。如果在事件触发过程中i
的值突然增加,将会导致你计算的间隔出现偏差。
解决方案取决于您想要做什么。
如果您只想每 N 秒执行一个方法,那么您假设计时器始终按照设定的时间间隔正确运行。
如果想在一定时间内执行某个方法N次,可以记录执行次数。如果在指定时间后执行次数不够,您可以额外添加一次(就像闰秒一样)。