![](/img/trans.png)
[英]access BackgroundService from controller in asp.net core 2.1
[英]How to run BackgroundService on a timer in ASP.NET Core 2.1
我想在 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 扩展回购协议上
03-2022更新,在底部阅读!
04-2020更新,在底部阅读!
@Panagiotis Kanavos 在我的问题的评论中给出了答案,但没有将其作为实际答案发布; 这个答案是献给他/她的。
我使用 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();
}
}
在我的例子中,我通过执行new Timer(async () => await DoWorkAsync(), ...)
使_timer
调用异步。
将来,可以编写一个扩展,使这样的类在扩展存储库中可用,因为我认为这非常有用。 我在描述中发布了 github 问题链接。
提示,如果您计划为多个托管服务重用此类,请考虑创建一个包含计时器和抽象PerformWork()
之类的基类,以便“时间”逻辑仅在一个地方。
谢谢您的回答。 我希望这对将来的人有帮助。
2020 年 4 月更新:
开箱即用的普通核心服务集合 DI 容器无法在此处注入范围内的服务。 我正在使用 autofac,由于注册错误,它使得在构造函数中使用像IClassRepository
这样的作用域服务成为可能,但是当我开始处理一个只使用AddScoped<>(), AddSingleton<>(), AddTransient<>()
我们发现注入作用域的东西是行不通的,因为你不在作用域上下文中。
为了使用您的作用域服务,注入一个IServiceScopeFactory
(更易于测试)并使用CreateScope()
,它允许您使用带有using
语句的scope.GetService()
:)
03-2022 更新:这篇文章获得了很多观点和关注,但我不得不说我不再是我的解决方案的忠实拥护者。 我会提出不同的解决方案:
此答案中发布的解决方案的缺点是:
async
支持。 我从来没有真正弄清楚这个解决方案是否“正确”Quartz.Net
确实支持这一点。实现这一目标的一种方法是使用 HangFire.io,这将处理预定的后台任务,管理跨服务器的平衡并且具有相当大的可扩展性。
请参阅https://www.hangfire.io上的经常性工作
这是基于以前的响应和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();
}
}
我找到了一个使用 .NET Core 内置功能的简单解决方案。 它直接使用BackgroundService
而不是IHostedService
和IDisposable
。 Philipp Bauknecht 的一篇博文启发了我: https ://medium.com/medialesson/run-and-manage-periodic-background-tasks-in-asp-net-core-6-with-c-578a31f4b7a3 他做了能够暂停服务是可以管理的。
注意:在 IIS 或 Azure App Service 中托管此应用程序时,请确保将应用程序设置为始终开启,否则托管服务将在一段时间后关闭
public class TimedBackgroundService : BackgroundService
{
private async Task ExecuteTaskAsync()
{
// ...
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
TimeSpan interval = TimeSpan.FromMinutes(60);
// Available with .NET 6.0
using PeriodicTimer timer = new PeriodicTimer(interval);
while (!stoppingToken.IsCancellationRequested &&
await timer.WaitForNextTickAsync(stoppingToken))
{
// Alternative to PeriodicTimer (available in .NET Core 2.1).
// REMARK: Calls would successively be delayed a little bit this way.
//await Task.Delay(interval, stoppingToken);
await ExecuteTaskAsync();
}
}
}
并且不要忘记在 Startup 中注册它。
services.AddHostedService<TimedBackgroundService>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.