C# 如何在ASP.NET Core 2.1中的计时器上运行BackgroundService
我想在ASP.NET Core 2.1中运行后台作业。它必须每2小时运行一次,并且需要访问我的DI容器,因为它将在数据库中执行一些清理。它需要是异步的,并且应该独立于我的ASP.NET Core 2.1应用程序运行 我看到有一个C# 如何在ASP.NET Core 2.1中的计时器上运行BackgroundService,c#,asp.net-core,background-process,asp.net-core-2.1,background-service,C#,Asp.net Core,Background Process,Asp.net Core 2.1,Background Service,我想在ASP.NET Core 2.1中运行后台作业。它必须每2小时运行一次,并且需要访问我的DI容器,因为它将在数据库中执行一些清理。它需要是异步的,并且应该独立于我的ASP.NET Core 2.1应用程序运行 我看到有一个IHostedService,但是ASP.NET Core 2.1还引入了一个名为BackgroundService的抽象类,它为我做了更多的工作。看起来不错,我想用它 不过,我还没有弄清楚如何在计时器上运行从BackgroundService派生的服务 我是否需要在Ex
IHostedService
,但是ASP.NET Core 2.1还引入了一个名为BackgroundService
的抽象类,它为我做了更多的工作。看起来不错,我想用它
不过,我还没有弄清楚如何在计时器上运行从BackgroundService
派生的服务
我是否需要在ExecuteAsync(token)
中配置它,方法是记住它上次运行的时间并确定这是否是2小时,还是有更好/更干净的方法只说它必须每2小时运行一次
另外,我使用后台服务解决问题的方法正确吗
谢谢大家!
编辑:
将此发布在上实现这一点的一种方法是使用HangFire.io,它将处理预定的后台任务,管理服务器间的平衡,并且具有相当的可扩展性
请参见更新于2020年4月的《重复性工作》,请在底部阅读强>
@Panagiotis Kanavos在对我的问题的评论中给出了答案,但没有将其作为实际答案发布;这个答案是献给他/她的
我使用了一个类似于MicrosoftDocs的工具来创建该服务
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();
}
}
内部类TimedHostedService:IHostedService,IDisposable
{
专用只读ILogger\u记录器;
私人定时器;
公共TimedHostedService(ILogger记录器)
{
_记录器=记录器;
}
公共任务StartSync(CancellationToken CancellationToken)
{
_logger.LogInformation(“定时后台服务正在启动”);
_计时器=新计时器(DoWork、null、TimeSpan.Zero、,
时间跨度从秒(5)开始;
返回Task.CompletedTask;
}
私有无效工作(对象状态)
{
_logger.LogInformation(“定时后台服务正在工作。”);
}
公共任务StopAsync(CancellationToken CancellationToken)
{
_logger.LogInformation(“定时后台服务正在停止”);
_计时器?更改(Timeout.Infinite,0);
返回Task.CompletedTask;
}
公共空间处置()
{
_计时器?.Dispose();
}
}
在我的例子中,我通过执行newtimer(async()=>wait-doworksync(),…)
使\u定时器调用async
将来,可以编写一个扩展,使这样的类在Extensions repo中可用,因为我认为这非常有用。我在描述中发布了github问题链接
一个提示,如果您打算重用多个托管服务的这个类,考虑创建一个基类,该基类包含计时器和抽象<代码> PrimWorkWord()/<代码>或某物,因此“时间”逻辑只在一个地方。
谢谢你的回答!我希望这对将来的人有所帮助
更新04-2020:
使用普通的核心服务集合DI容器(开箱即用),在这里注入作用域服务是不可能的。我使用的是autofac,由于注册错误,它可以在构造函数中使用作用域服务,如IClassRepository
,但当我开始处理一个仅使用AddScoped()、AddSingleton()、AddTransient()的不同项目时
我们发现注入作用域的东西不起作用,因为您不在作用域上下文中
为了使用您的作用域服务,请插入一个IServiceScopeFactory
(更易于测试)并使用CreateScope()
,它允许您使用scope.GetService()
和using
语句:)基于以前的响应和
改进:
在上一个任务完成执行之前,它不会启动计时器。这将有助于避免陷入两个任务同时执行的情况
它支持异步任务
它在任务执行期间处理可能的异常,以确保不会阻止下一个任务的执行
对于执行的每个任务,都会创建一个作用域,因此您可以访问RunJobAsync中的任何作用域服务
可以在继承的类中指定间隔和初始任务执行时间
访问作用域服务示例
protected override async Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken)
{
DbContext context = serviceProvider.GetRequiredService<DbContext>();
}
受保护的覆盖异步任务RunJobAsync(IServiceProvider服务提供程序,CancellationToken stoppingToken)
{
DbContext上下文=serviceProvider.GetRequiredService();
}
源代码:
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();
}
}
公共抽象类TimedHostedService:IHostedService,IDisposable
{
专用只读ILogger\u记录器;
私人定时器;
私有任务(executingTask);;
私有只读CancellationTokenSource _stoppingCts=new CancellationTokenSource();
IServiceProvider服务;
公共时间后台服务(IServiceProvider服务)
{
_服务=服务;
_记录器=_services.GetRequiredService();
}
公共任务StartSync(CancellationToken CancellationToken)
{
_计时器=新计时器(ExecuteTask,null,FirstRunAfter,TimeSpan.FromMissels(-1));
返回Task.CompletedTask;
}
私有void ExecuteTask(对象状态)
{
_计时器?更改(Timeout.Infinite,0);
_executingTask=ExecuteTaskAsync(_stoppingCts.Token);
}
专用异步任务ExecuteTaskAsync(CancellationToken stoppingToken)
{
尝试
{
使用(var scope=\u services.CreateScope())
{
等待RunJobAsync(scope.ServiceProvider,stoppingToken);
}
}
捕获(异常)
{
_logger.LogError(“BackgroundTask失败”,异常);
}
_定时器