1、在 ASP.NET Core 中使用托管服务实现后台任务
很多时候我们需要给程序添加一些后台任务,帮我处理一些需要定时处理的任务,比如定时发送邮件,定时做一些统计之类的工作,这时候我们可以写一个windows服务来搞定,或者在现有的webapi项目中添加定时。
写windows服务来处理后台任务固然是个比较好的选择,但是这样就会增加运维成本,原本只有一个项目,现在有两个,最主要的是windows服务很容易被遗忘掉,升级或者迁移的时候增加风险,对于一些比较简单的任务,我选择直接在 ASP.NET Core 中使用托管服务实现后台任务,非常的简单。这样就可以在一起维护和管理。
编写一个定时任务有两个步骤:
1、实现IHostedService,IDisposable这两个接口,或者直接继承BackgroundService抽象类(因为它实现IHostedService,IDisposable),看代码:
//实现接口:IHostedService,IDisposable
public class EmailBackgroundService : IHostedService,IDisposable
{
//定义一个定时器
private Timer _timer;
/// <summary>
/// 启动任务绑定
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
Common.WriteEmailLog("定时任务被启动", "...start...");
//绑定定时任务
//设置延迟时间
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(60 * Interval));
return Task.CompletedTask;
}
/// <summary>
/// 定时执行的操作,绑定到定时器上
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
private void DoWork(object state)
{
Common.WriteEmailLog("定时任务被触发", "开始一波邮件发送");
try
{
//一波操作
}
catch (Exception ex)
{
Common.WriteEmailLog("定时发送邮件时报错", ex.Message);
}
}
/// <summary>
/// 任务关闭时执行
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
Common.WriteEmailLog("定时任务被关闭", "...end...");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
/// <summary>
/// 释放托管资源,释放时触发
/// </summary>
public void Dispose()
{
Common.WriteEmailLog("定时任务被释放闭", "...Dispose...");
_timer?.Dispose();
//iis会回收这个定时任务,这边在回收的时候触发一个请求,来再次唤醒该服务
Thread.Sleep(5000);
HttpHelper.HttpGet(_configuration.GetSection("AwakenUrl").Value);
}
}
2、在Startup类中注册后台任务:
//配置后台任务
//services.AddTransient(typeof(Microsoft.Extensions.Hosting.IHostedService), typeof(EmailBackgroundService));
services.AddHostedService<EmailBackgroundService>();
2、解决iis自动回收导致任务被终止的问题
IIS会定时回收,类似于自动重新启动网站,我们都有知道网站启动后第一次访问往往会比较慢,网站启动后没有访问,过段时间iis会回收,请求再来的时候会和网站刚刚启动的时候一样,需要等待一段时间,很不舒服,这个就是因为iis回收导致的。
定时回收除了会出现上面的情况外,还会将我们托管的后台任务回收掉,导致我们的后台任务终止执行,直到进来一个请求(网站内任意地址)任务启动,那么如果请求很久没来,这个任务将无法被启动,错过任务应该执行的档口。虽然我们可以通过调整自动回收的时间,甚至设置他不自动回收,但这样始终不是最好的解决方案。
定时任务是托管的资源,实现了IDisposable接口,并且实现了Dispose()方法,这个方法在回收的时候会被触发。通过这一点我们可以在Dispose()方法中,再请求一下我们的网站或接口(请求地址尽量选择资源消耗比较小的),通过访问这样一个请求,我们服务又会起来。
上面红字部分是我原先介绍的一种方法,没有做过全面的测试就搬上来,最近发现它同样会停掉,特地来说明一下,希望大家不要走弯路,这边我再介绍另外一个方案,就是通过配置iis来实现预加载,即在资源被回收的情况下,通过一个链接来唤醒自身,这样首次访问的时候不会觉得卡顿,并且我们被回收掉的定时任务又会重新的启动起来,一举两得,具体的配置方法如下:
1、设置网站的启用预加载:
2、编辑配置
3、设置应用程序池 启动模式为:AlwaysRunning
经过上面的一顿操作,就配置好了,为了保险起见,设置定时任务一定要留心它的运行情况,以免产生不必要的麻烦,最好记录日志,关注下夜间网站闲置,被回收时的运行情况,保证稳定,如果你的任务仍有异常,那么最好写一个服务来跑定时任务。