更新于2022年3月,详见底部!
更新于2020年4月,详见底部!
@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
调用异步化。
未来可以编写一个扩展程序,将这样的类放在Extensions仓库中,因为我认为这非常有用。我在说明中发布了github问题链接。
提示:如果您计划将此类用于多个托管服务,请考虑创建包含定时器和抽象PerformWork()
(或其他类似内容)的基类,以便“时间”逻辑仅存在于一个位置。
感谢您的回答!我希望这能帮助未来的某个人。
更新 04-2020:
在正常的Core服务集合DI容器中,无法在此处注入作用域服务。我使用的是autofac,因为注册错误,所以可以在构造函数中使用作用域服务例如IClassRepository
,但是当我开始处理另一个只使用AddScoped<>(),AddSingleton<>(),AddTransient<>()
的项目时,我们发现注入作用域的东西不起作用,因为您不在范围的上下文中。
为了使用您的作用域服务,请注入IServiceScopeFactory
(易于测试)并使用CreateScope()
,这允许您在using
语句中使用scope.GetService()
:)
更新 03-2022:
这篇文章得到了很多的浏览和关注,但我必须说我不再是我解决方案的粉丝了。我建议使用不同的解决方案:
- 使用hangfire或quartz代替,如果您只想让代码在backgroundservice中运行
- 如果您在kubernetes环境中运行,请查看kubernetes cronjobs
- 这样做的好处是仅在需要时运行您的代码,与全天候运行项目并每天凌晨3点执行一次作业相比,可以节省资源
- 请查看Azure Functions / AWS Lambda定时器
- 这可能比制作自己的定时托管服务更便宜,更易于维护。但是,将其集成到k8s环境中可能更加困难。
此答案的缺点包括:
- 您需要自己管理许多其他选项免费提供的东西。例如:
- 如果应用程序在应该运行作业时关闭会怎么样?
- 如果您的作业时间太长并且另一个作业开始了怎么办?
- 记录和监视。
- 我仍然不确定此解决方案中的
async
支持。我从未真正弄清楚是否正确。
- 我也不喜欢DI没有默认支持。
Quartz.Net
支持此功能。
- 与quartz相比,它不够灵活。
DoWork()
不是异步的。我可以将DoWork()
标记为异步,但这并不是正确的方法,因为它不会被等待执行。 - S. ten BrinkeIHostedService
,然后使用计时器会比使用BackgroundService
并通过计时器检查是否想要在ExecuteAsync
中运行工作更好吗?(再次发表您的回答+为什么这种方法比此方法更好的原因)我知道我的方法会导致在不执行任务的情况下调用ExecuteAsync
,但是我的问题是:如果不能将其放在计时器上,那么BackgroundService
的意义何在?接着问:为什么没有一个TimedBackgroundService
呢? - S. ten BrinkeTimer: var timer = new System.Threading.Timer(async (e) => { await Task.Delay(500); Console.WriteLine("Tick"); }, null, 0, 5000);
然而,该线程说需要使用try/catch来管理异常。如果发生未处理的异常,计时器可能会停止工作。再次强调,这些应该作为实际答案提出,以便更容易地讨论。 - S. ten Brinke