Appearance
AspNetCore 后台任务
1. 在 ASP.NET Core 中使用托管服务实现后台任务
很多时候我们需要给程序添加一些后台任务,帮我处理一些需要定时处理的任务,比如定时发送邮件,定时做一些统计之类的工作,这时候我们可以写一个 Windows 服务来搞定,或者在现有的 WebApi 项目中添加定时。
写 Windows 服务来处理后台任务固然是个比较好的选择,但是这样就会增加运维成本,原本只有一个项目,现在有两个,最主要的是 Windows 服务很容易被遗忘掉,升级或者迁移的时候增加风险,对于一些比较简单的任务,我选择直接在 ASP.NET Core 中使用托管服务实现后台任务,非常的简单。这样就可以在一起维护和管理。
编写一个定时任务有两个步骤:
实现
IHostedService,IDisposable这两个接口,或者直接继承BackgroundService抽象类(因为它实现IHostedService、IDisposable),看代码:csharp// 实现接口: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); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62在
Startup类中注册后台任务:csharp// 配置后台任务 // services.AddTransient(typeof(Microsoft.Extensions.Hosting.IHostedService), typeof(EmailBackgroundService)); services.AddHostedService<EmailBackgroundService>();1
2
3
2. 解决 IIS 自动回收导致任务被终止的问题
IIS 会定时回收,类似于自动重新启动网站,我们都有知道网站启动后第一次访问往往会比较慢,网站启动后没有访问,过段时间 IIS 会回收,请求再来的时候会和网站刚刚启动的时候一样,需要等待一段时间,很不舒服,这个就是因为 IIS 回收导致的。
定时回收除了会出现上面的情况外,还会将我们托管的后台任务回收掉,导致我们的后台任务终止执行,直到进来一个请求(网站内任意地址)任务启动,那么如果请求很久没来,这个任务将无法被启动,错过任务应该执行的档口。虽然我们可以通过调整自动回收的时间,甚至设置他不自动回收,但这样始终不是最好的解决方案。
定时任务是托管的资源,实现了 IDisposable 接口,并且实现了 Dispose() 方法,这个方法在回收的时候会被触发。通过这一点我们可以在 Dispose() 方法中,再请求一下我们的网站或接口(请求地址尽量选择资源消耗比较小的),通过访问这样一个请求,我们服务又会起来。
上面删除部分是我原先介绍的一种方法,没有做过全面的测试就搬上来,最近发现它同样会停掉,特地来说明一下,希望大家不要走弯路,这边我再介绍另外一个方案,就是通过配置 IIS 来实现预加载,即在资源被回收的情况下,通过一个链接来唤醒自身,这样首次访问的时候不会觉得卡顿,并且我们被回收掉的定时任务又会重新的启动起来,一举两得,具体的配置方法如下:
设置网站的启用预加载:

编辑配置


设置应用程序池 启动模式为:
AlwaysRunning
经过上面的一顿操作,就配置好了,为了保险起见,设置定时任务一定要留心它的运行情况,以免产生不必要的麻烦,最好记录日志,关注下夜间网站闲置,被回收时的运行情况,保证稳定,如果你的任务仍有异常,那么最好写一个服务来跑定时任务。