我将为此添加一个答案,因为这是在 ASP.NET Core 中完成此类事情的唯一合乎逻辑的方法:IHostedService 实现。
这是一个实现IHostedService的不可重入定时器后台服务。
public sealed class MyTimedBackgroundService : IHostedService
{
private const int TimerInterval = 5000; // change this to 24*60*60 to fire off every 24 hours
private Timer _t;
public async Task StartAsync(CancellationToken cancellationToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync();
// set up a timer to be non-reentrant, fire in 5 seconds
_t = new Timer(async _ => await OnTimerFiredAsync(),
null, TimerInterval, Timeout.Infinite);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_t?.Dispose();
return Task.CompletedTask;
}
private async Task OnTimerFiredAsync()
{
try
{
// do your work here
Debug.WriteLine($"{TimerInterval / 1000} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
finally
{
// set timer to fire off again
_t?.Change(TimerInterval, Timeout.Infinite);
}
}
}
所以,我知道我们在 cmets 中讨论过这个,但是 System.Threading.Timer 回调方法被认为是一个事件处理程序。在这种情况下使用async void 是perfectly acceptable,因为将在线程池线程上引发转义该方法的异常,就像该方法是同步的一样。无论如何,您可能应该在其中抛出 catch 以记录任何异常。
您提出计时器在某个间隔边界不安全。我从高处和低处寻找该信息,但找不到。我以 24 小时间隔、2 天间隔、2 周间隔使用计时器......我从来没有让它们失败过。我也有很多年在生产服务器的 ASP.NET Core 中运行。我们现在应该已经看到了。
好的,所以你还是不信任System.Threading.Timer...
让我们这么说吧,不... 没有什么可怕的方式可以让你使用计时器。好的,没关系……我们走另一条路吧。让我们从IHostedService 移动到BackgroundService(这是IHostedService 的实现),然后简单地倒计时。
这将减轻对计时器边界的担忧,您不必担心async void 事件处理程序。这也是免费的不可重入。
public sealed class MyTimedBackgroundService : BackgroundService
{
private const long TimerIntervalSeconds = 5; // change this to 24*60 to fire off every 24 hours
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync(stoppingToken);
var countdown = TimerIntervalSeconds;
while (!stoppingToken.IsCancellationRequested)
{
if (countdown-- <= 0)
{
try
{
await OnTimerFiredAsync(stoppingToken);
}
catch(Exception ex)
{
// TODO: log exception
}
finally
{
countdown = TimerIntervalSeconds;
}
}
await Task.Delay(1000, stoppingToken);
}
}
private async Task OnTimerFiredAsync(CancellationToken stoppingToken)
{
// do your work here
Debug.WriteLine($"{TimerIntervalSeconds} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
}
一个额外的副作用是您可以使用 long 作为您的间隔,让您有超过 25 天的时间触发事件,而 Timer 的上限为 25 天。
您可以像这样注入其中任何一个:
services.AddHostedService<MyTimedBackgroundService>();