如何在ASP.NET Core 2.1中的计时器上运行BackgroundService

时间:2018-12-11 15:56:53

标签: c# asp.net-core background-process asp.net-core-2.1 background-service

我想在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

3 个答案:

答案 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

的改进版本

改进:

  1. 直到上一个任务执行完毕,它才会启动计时器。避免陷入同时执行两个任务的情况会有所帮助。
  2. 支持异步任务
  3. 它会处理任务执行期间可能出现的异常,以确保不会阻止下一个任务的执行。
  4. 为每个执行范围的任务创建一个范围,因此您可以在 RunJobAsync 中访问任何范围内的服务
  5. 您可以在继承的类中指定间隔和初始任务执行时间。

访问范围服务示例

    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();
    }
}