我想在ASP.NET Core 2.1中运行后台作业。它必须每2小时运行一次,并且将需要访问我的DI容器,因为它将在数据库中执行一些清理。它必须为async
,并且应独立于我的ASP.NET Core 2.1应用程序运行。
我看到有一个IHostedService
,但是ASP.NET Core 2.1还引入了一个名为BackgroundService
的抽象类,它可以为我做更多的工作。看起来不错,我想使用它!
但是,我无法弄清楚如何在计时器上运行从BackgroundService
派生的服务。
我需要在ExecuteAsync(token)
中进行配置吗,要记住它的上一次运行时间,弄清楚这是否是2个小时,或者是否有更好/更干净的方法只是说它必须每次运行2小时?
此外,使用BackgroundService
解决问题的方法正确吗?
谢谢!
编辑:
将此发布到MS extensions repo
答案 0 :(得分:2)
实现此目标的一种方法是使用HangFire.io,它将处理计划的后台任务,管理服务器之间的平衡并且具有很好的可伸缩性。
请参阅https://www.hangfire.io上的重复作业
答案 1 :(得分:0)
@Panagiotis Kanavos在我的问题的评论中给出了答案,但并未将其发布为实际答案;这个答案是献给他/她的。
我使用了Timed background service(类似于Microsoft文档中的那个)来创建服务。
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
在我的情况下,我通过执行_timer
使new Timer(async () => await DoWorkAsync(), ...)
异步调用。
将来,可能会编写一个扩展,以使此类在扩展库中可用,因为我认为这很有用。我在说明中张贴了github问题链接。
提示,如果计划将此类用于多个托管服务,请考虑创建一个包含计时器和抽象PerformWork()
或类似内容的基类,以便“时间”逻辑仅在一个地方。 / p>
谢谢您的回答!我希望这对以后的人有所帮助。
答案 2 :(得分:0)
这是基于以前的回复和https://stackoverflow.com/a/56666084/1178572
的改进版本改进:
访问范围服务示例
protected override async Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken)
{
DbContext context = serviceProvider.GetRequiredService<DbContext>();
}
源代码:
public abstract class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
IServiceProvider _services;
public TimedHostedService(IServiceProvider services)
{
_services = services;
_logger = _services.GetRequiredService<ILogger<TimedHostedService>>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(ExecuteTask, null,FirstRunAfter, TimeSpan.FromMilliseconds(-1));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
_timer?.Change(Timeout.Infinite, 0);
_executingTask = ExecuteTaskAsync(_stoppingCts.Token);
}
private async Task ExecuteTaskAsync(CancellationToken stoppingToken)
{
try
{
using (var scope = _services.CreateScope())
{
await RunJobAsync(scope.ServiceProvider, stoppingToken);
}
}
catch (Exception exception)
{
_logger.LogError("BackgroundTask Failed", exception);
}
_timer.Change(Interval, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken);
protected abstract TimeSpan Interval { get; }
protected abstract TimeSpan FirstRunAfter { get; }
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
public void Dispose()
{
_stoppingCts.Cancel();
_timer?.Dispose();
}
}