26. 定时任务/后台任务
版本说明
以下内容仅限 Furion 2.0.0 +
版本使用。
IIS 部署说明
由于 IIS 有回收的机制,所以定时任务应该采用独立部署,不然经常出现不能触发的情况。查看【Worker Service 章节】
26.1 关于定时任务
顾名思义,定时任务就是在特定的时间或符合某种时间规律执行的任务。通常定时任务有四种时间调度方式:
缓隔时间
方式:延迟多少时间后调配任务,这种方式任务只会被调用一次。间隔时间
方式:每隔一段固定时间调配任务,无间断调用任务。Cron 表达式
方法:通过Cron
表达式计算下一次执行时间进行调配任务,可以配置特定时间范围内执行,也可以无间断执行。自定义下次执行时间
:可以通过各种逻辑运算返回下一次执行时间
26.2 如何实现
Furion
框架提供了两种方式实现定时任务:
SpareTime
静态类:SpareTime
静态类提供SpareTime.Do([options])
方式调用。ISpareTimeWorker
依赖方式:通过自定义类实现ISpareTimeWorker
接口并编写一定规则的方法即可。需要在Startup.cs
中注册services.AddTaskScheduler()
26.3 缓隔方式使用
26.3.1 特定时间后执行
这里演示 3s
后执行
Console.WriteLine("当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
// timer 是定时器的对象,包含定时器相关信息
// count 表示执行次数,这里只有一次
SpareTime.DoOnce(3000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
});
26.3.2 配置任务信息
SpareTime.DoOnce(3000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}, "jobName", "描述一下这个任务是干什么的");
jobName
标识任务的唯一标识,通过这个标识可以启动、暂停、销毁任务。
26.3.3 手动启动执行
默认情况下,任务初始化后就立即启动,等待符合的时间就执行,有些时候我们仅仅想初始化时间,不希望立即执行,只需要配置 startNow
即可:
SpareTime.DoOnce(3000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
},"jobName", startNow: false);
// 手动启动执行
SpareTime.Start("jobName");
26.3.4 模拟后台执行
有些时候,我们只需要开启新线程去执行一个任务,比如发短信,发邮件,无需配置。
// 此方法无需主线程等待即可返回,可大大提高性能
SpareTime.DoIt(() => {
// 这里发送短信,发送邮件或记录访问记录
});
还可以指定多长时间后触发,建议 10-1000
毫秒之间:
SpareTime.DoIt(() => {
// 发送短信
}, 100);
26.3.5 ISpareTimeWorker
方式
public class JobWorker : ISpareTimeWorker
{
/// <summary>
/// 3s 后执行
/// </summary>
/// <param name="timer"></param>
/// <param name="count"></param>
[SpareTime(3000, "jobName", DoOnce = true, StartNow = true)]
public void DoSomething(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
/// <summary>
/// 3s 后执行(支持异步)
/// </summary>
/// <param name="timer"></param>
/// <param name="count"></param>
[SpareTime(3000, "jobName", DoOnce = true, StartNow = true)]
public async Task DoSomethingAsync(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await Task.Completed;
}
}
需要在 Startup.cs
中注册 services.AddTaskScheduler()
26.4 间隔方式使用
26.4.1 每隔一段时间执行
// 每隔 1s 执行
SpareTime.Do(1000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
});
26.4.2 配置任务信息
SpareTime.Do(1000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, "jobName", "这是一个计时器任务");
26.4.3 手动启动执行
SpareTime.Do(1000, (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, "jobName", startNow:false);
SpareTime.Start("jobName");
26.4.4 ISpareTimeWorker
方式
public class JobWorker : ISpareTimeWorker
{
/// <summary>
/// 每隔 3s 执行
/// </summary>
/// <param name="timer"></param>
/// <param name="count"></param>
[SpareTime(3000, "jobName", StartNow = true)]
public void DoSomething(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
}
需要在 Startup.cs
中注册 services.AddTaskScheduler()
26.5 Cron
表达式使用
26.5.1 什么是 Cron
表达式
Cron 表达式是一个字符串,字符串以 5
或 6
个空格隔开,分为 6 或 7 个域,每一个域代表一个含义,Cron 有如下两种语法格式:
(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year
(2)Seconds Minutes Hours DayofMonth Month DayofWeek
Cron 从左到右(用空格隔开):秒
分
小时
月份中的日期
月份
星期中的日期
年份
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59 的整数 | , - \* / 四个字符 |
分(Minutes) | 0~59 的整数 | , - \* / 四个字符 |
小时(Hours) | 0~23 的整数 | , - \* / 四个字符 |
日期(DayofMonth) | 1~31 的整数(但是你需要考虑平闰月的天数) | ,- \* ? / L W C 八个字符 |
月份(Month) | 1~12 的整数或者 JAN-DEC | , - \* / 四个字符 |
星期(DayofWeek) | 1~7 的整数或者 SUN-SAT (1=SUN) | , - \* ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - \* / 四个字符 |
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)_
:表示匹配该域的任意值。假如在 Minutes
域使用 \_
, 即表示每分钟都会触发事件。
(2)?
:只能用在 DayofMonth
和 DayofWeek
两个域。它也匹配域的任意值,但实际不会。因为 DayofMonth
和 DayofWeek
会相互影响。例如想在每月的 20
日触发调度,不管 20
日到底是星期几,则只能使用如下写法: 13 13 15 20 _ ?
, 其中最后一位只能用?
,而不能使用_,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-
:表示范围。例如在 Minutes
域使用 5-20
,表示从 5
分到 20
分钟每分钟触发一次
(4)/
:表示起始时间开始触发,然后每隔固定时间触发一次。例如在 Minutes
域使用 5/20
,则意味着 5
分钟触发一次,而 25,45
等分别触发一次.
(5),
:表示列出枚举值。例如:在 Minutes
域使用 5,20
,则意味着在 5
和 20
分每分钟触发一次。
(6)L
:表示最后,只能出现在 DayofWeek
和 DayofMonth
域。如果在 DayofWeek
域使用 5L
,意味着在最后的一个星期四触发。
(7)W
:表示有效工作日(周一到周五) 只能出现在 DayofMonth
域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth
使用 5W
,如果 5
日是星期六,则将在最近的工作日:星期五,即 4
日触发。如果 5
日是星期天,则在 6
日(周一)触发;如果 5
日在星期一到星期五中的一天,则就在 5
日触发。另外一点,W
的最近寻找不会跨过月份 。
(8)LW
:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#
:用于确定每个月第几个星期几,只能出现在 DayofMonth
域。例如在 4#2
,表示某月的第二个星期三。
26.5.2 常见 Cron
表达式
表达式 | 表达式代表含义 | 格式化 |
---|---|---|
* * * * * | 每分钟 | CronFormat.Standard |
*/1 * * * * | 每分钟 | CronFormat.Standard |
0 0/1 * * * ? | 每分钟 | CronFormat.IncludeSeconds |
0 0 * * * ? | 每小时 | CronFormat.IncludeSeconds |
0 0 0/1 * * ? | 每小时 | CronFormat.IncludeSeconds |
0 23 ? * MON-FRI | 晚上 11:00,周一至周五 | CronFormat.Standard |
* * * * * * | 每秒 | CronFormat.IncludeSeconds |
*/45 * * * * * | 每 45 秒 | CronFormat.IncludeSeconds |
*/5 * * * * | 每 5 分钟 | CronFormat.Standard |
0 0/10 * * * ? | 每 10 分钟 | CronFormat.IncludeSeconds |
0 */5 * * * * | 每 5 分钟 | CronFormat.IncludeSeconds |
30 11 * * 1-5 | 周一至周五上午 11:30 | CronFormat.Standard |
30 11 * * * | 11:30 | CronFormat.Standard |
0-10 11 * * * | 上午 11:00 至 11:10 之间的每一分钟 | CronFormat.Standard |
* * * 3 * | 每分钟,只在 3 月份 | CronFormat.Standard |
* * * 3,6 * | 每分钟,只在 3 月和 6 月 | CronFormat.Standard |
30 14,16 * * * | 下午 02:30 分和 04:30 分 | CronFormat.Standard |
30 6,14,16 * * * | 早上 06:30,下午 02:30 和 04:30 | CronFormat.Standard |
46 9 * * 1 | 早上 09:46,只在星期一 | CronFormat.Standard |
23 12 15 * * | 下午 12:23,在本月的第 15 天 | CronFormat.Standard |
23 12 * JAN * | 下午 12:23,只在 1 月份 | CronFormat.Standard |
23 12 ? JAN * | 下午 12:23,只在 1 月份 | CronFormat.Standard |
23 12 * JAN-FEB * | 下午 12:23,1 月至 2 月 | CronFormat.Standard |
23 12 * JAN-MAR * | 下午 12:23,1 月至 3 月 | CronFormat.Standard |
23 12 * * SUN | 下午 12:23,仅在星期天 | CronFormat.Standard |
*/5 15 * * MON-FRI | 每 5 分钟,下午 0:00 至 03:59,周一至周五 | CronFormat.Standard |
* * * * MON#3 | 每分钟,在月的第三个星期一 | CronFormat.Standard |
* * * * 4L | 每一分钟,在本月的最后一天 | CronFormat.Standard |
*/5 * L JAN * | 每月一次每月 5 分钟,只在 1 月份 | CronFormat.Standard |
30 02 14 * * * | 下午在 02:02:30 | CronFormat.IncludeSeconds |
5-10 * * * * * | 每分钟的 5-10 秒 | CronFormat.IncludeSeconds |
5-10 30-35 10-12 * * * | 10:00 至 12:00 之间的每分钟 5-10 秒,每小时 30-35 分钟 | CronFormat.IncludeSeconds |
30 */5 * * * * | 每分钟的 30 秒,每五分钟 | CronFormat.IncludeSeconds |
0 30 10-13 ? * WED,FRI | 每小时的 30 分钟,下午 10:00 至 01:00 之间,仅在周三和周五 | CronFormat.IncludeSeconds |
10 0/5 * * * ? | 每分钟的 10 秒,每 05 分钟 | CronFormat.IncludeSeconds |
0 0 6 1/1 * ? | 下午 06:00 | CronFormat.IncludeSeconds |
0 5 0/1 * * ? | 一个小时的 05 分 | CronFormat.IncludeSeconds |
0 0 L * * | 每月最后一天上午 00:00 | CronFormat.Standard |
0 0 L-1 * * | 每月最后一天的凌晨 00:00 | CronFormat.Standard |
0 0 3W * * | 每月第 3 个工作日上午 00:00 | CronFormat.Standard |
0 0 LW * * | 在每月的最后一个工作日,上午 00:00 | CronFormat.Standard |
0 0 * * 2L | 本月最后一个星期二上午 00:00 | CronFormat.Standard |
0 0 * * 6#3 | 每月第三个星期六上午 00:00 | CronFormat.Standard |
0 0 ? 1 MON#1 | 1 月第一个星期一上午 00:00 | CronFormat.Standard |
26.5.3 在线生成 Cron
表达式
26.5.4 Macro
标识符
为了方便定义 Cron
表达式,Furion
框架也提供了非常方便的占位符实现常用的时间格式:
占位符 | 对应表达式 | 占位符代表含义 |
---|---|---|
@every_second | * * * * * * | 一秒钟跑一次 |
@every_minute | * * * * * | 在分钟开始时每分钟运行一次 |
@hourly | 0 * * * * | 在小时开始时每小时运行一次 |
@daily | 0 0 * * * | 每天午夜运行一次 |
@midnight | 0 0 * * * | 每天午夜运行一次 |
@weekly | 0 0 * * 0 | 周日上午午夜每周运行一次 |
@monthly | 0 0 1 * * | 每月在每月第一天的午夜运行一次 |
@yearly | 0 0 1 1 * | 每年 1 月 1 日午夜运行一次 |
@annually | 0 0 1 1 * | 每年 1 月 1 日午夜运行一次 |
26.5.5 使用 Cron
表达式
// 每隔 1s 执行
SpareTime.Do("* * * * * *", (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, cronFormat: CronFormat.IncludeSeconds);
关于 CronFormat
默认情况下,Furion
框架未启用对 秒
的支持,如需开启,则设置 cronFormat: CronFormat.IncludeSeconds
即可。默认值是 cronFormat: CronFormat.Standard
26.5.6 使用 Macro
占位符
// 每隔 1s 执行
SpareTime.Do("@every_second", (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
});
26.5.7 配置任务信息
SpareTime.Do("* * * * *", (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, "cronName", "每分钟执行一次");
26.5.8 手动启动执行
SpareTime.Do("* * * * *", (timer, count) => {
Console.WriteLine("现在时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, "cronName", "每分钟执行一次", startNow: false);
SpareTime.Start("cronName");
26.5.9 ISpareTimeWorker
方式
public class JobWorker : ISpareTimeWorker
{
/// <summary>
/// 每分钟执行
/// </summary>
/// <param name="timer"></param>
/// <param name="count"></param>
[SpareTime("* * * * *", "jobName", StartNow = true)]
public void DoSomething(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
/// <summary>
/// 每分钟执行(支持异步)
/// </summary>
/// <param name="timer"></param>
/// <param name="count"></param>
[SpareTime("* * * * *", "jobName", StartNow = true)]
public async Task DoSomethingAsync(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
await Task.Completed;
}
}
26.6 自定义下次执行时间
有些时候我们需要进行一些业务逻辑,比如数据库查询等操作返回下一次执行时间,这个时候我们可以通过高级自定义方式。
26.6.1 高级自定义间隔方式
SpareTime.Do(()=>{
// 这里可以查询数据库或进行或进行任何业务逻辑
if(符合逻辑){
return 1000; // 每秒执行
}
else return -1; // 不符合逻辑取消任务
},
(timer,count)=>{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
});
配置是否持续检查
默认情况下,该自定义会在返回 小于或等于0
时终止任务的执行。但是我们希望该任务不要终止,只要符合条件都一直执行,只需要配置 cancelInNoneNextTime: false
即可
26.6.2 高级自定义 Cron
表达式
SpareTime.Do(()=>{
// 这里可以查询数据库或进行或进行任何业务逻辑
if(符合逻辑){
return DateTimeOffset.Now.AddMinutes(10); // 十分钟后再执行
}
else return null; // 不符合逻辑取消任务
},
(timer,count) => {
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
});
配置是否持续检查
默认情况下,该自定义会在返回 null
时终止任务的执行。但是我们希望该任务不要终止,只要符合条件都一直执行,只需要配置 cancelInNoneNextTime: false
即可,如:
SpareTime.Do(()=>{
// 这里可以查询数据库或进行或进行任何业务逻辑
if(符合逻辑){
return SpareTime.GetCronNextOccurrence("cron 表达式");
}
else return null; // 不符合逻辑继续检查
},
(timer,count) => {
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}, cancelInNoneNextTime: false);
26.7 ISpareTimeWorker
说明
除了上面的 SpareTime
静态类方式,Furion
框架还提供了 ISpareTimeWorker
方式,使用该方式非常简单,只需要自定义一个公开且非抽象非静态类并实现 ISpareTimeWorker
即可。
在该类中定义的任务方法需满足以下规则:
- 必须是公开且实例方法
- 该方法必须返回
void
且提供SpareTimer
和long
两个参数 - 必须贴有
[SpareTime]
特性
如:
public class JobWorker : ISpareTimeWorker
{
// 每隔一秒执行,且立即启动
[SpareTime(1000, "jobName1", StartNow = true)]
public void DoSomething1(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 每分钟执行,且立即启动
[SpareTime("* * * * *", "jobName2", StartNow = true)]
public void DoSomething2(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 每秒执行,且等待启动
[SpareTime("* * * * * *", "jobName3",CronFormat = CronFormat.IncludeSeconds, StartNow = false)]
public void DoSomething3(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 每秒执行一次,每分钟也执行一次
[SpareTime(1000, "jobName4", StartNow = true)]
[SpareTime("* * * * *", "jobName5", StartNow = true)]
public void DoSomething4(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 只执行一次
[SpareTime(1000, "jobName5", StartNow = true, DoOnce = true)]
public void DoSomething5(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 读取配置文件,通过 #(配置路径)
[SpareTime("#(MyJob:Time)", "jobName6", StartNow = true, DoOnce = true)]
public void DoSomething5(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
// 支持异步
[SpareTime(1000, "jobName1", StartNow = true)]
public async Task DoSomethingAsync(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
await Task.Completed;
}
}
关于依赖注入
ISpareTimeWorker
接口主要是用来查找定时器对象的,也就是它的实现类并未提供依赖注入功能,所以在实现类并不支持构造函数注入依赖项。
26.7.1 [SpareTime]
特性
[SpareTime]
支持以下配置属性
Interval
:间隔时间,double
类型CronExpression
:Cron
表达式,string
类型WorkerName
:任务唯一标识,string
类型,必填
Description
:任务描述,string
类型DoOnce
:是否只执行一次,bool
类型,默认false
StartNow
:是否立即启动,默认false
CronFormat
:Cron
表达式格式化方式,CronFormat
枚举类型,默认CronFormat.Standard
ExecuteType
:配置任务执行方式,SpareTimeExecuteTypes
枚举类型,默认SpareTimeExecuteTypes.Parallel
26.8 SpareTime
静态类
SpareTime
静态类提供了一些方法方便初始化和管理任务
26.8.1 初始化任务
// 开启间隔任务
SpareTime.Do(interval, [options]);
// 开启 Cron 表达式任务
SpareTime.Do(expression, [options]);
// 只执行一次任务
SpareTime.DoOnce(interval, [options]);
// 实现自定义任务
SpareTime.Do(()=>{
return DateTime.Now.AddMinutes(10);
},[options]);
26.8.2 实现后台执行
// 实现后台执行
SpareTime.DoIt(()=>{});
26.8.3 开始一个任务
SpareTime.Start("任务标识");
26.8.4 暂停一个任务
SpareTime.Stop("任务标识");
// 还可以标记一个任务执行失败
SpareTime.Stop("任务标识", true);
26.8.5 取消一个任务
SpareTime.Cancel("任务名称");
26.8.6 销毁所有任务
SpareTime.Dispose();
26.8.7 获取所有任务
var workers = SpareTime.GetWorkers();
26.8.8 获取单个任务
var worker = SpareTime.GetWorker("workerName");
26.8.9 解析 Cron
表达式
var nextTime = SpareTime.GetCronNextOccurrence("* * * * *");
26.9 并行
和串行
执行方式
Furion
框架提供了任务两种执行方式 并行
和 串行
:
并行
:无需等待上一次任务完成,默认值串行
:需等待上一次任务完成
26.9.1 SpareTime
静态方式指定
SpareTime.Do(1000, (t, i) =>
{
Thread.Sleep(5000); // 模拟执行耗时任务
Console.WriteLine($"{t.WorkerName} -{t.Description} - {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {i}");
}, "serialName", "模拟串行任务", executeType: SpareTimeExecuteTypes.Serial);
26.9.2 ISpareTimeWorker
方式
[SpareTime(1000, "jobName1", StartNow = true, ExecuteType = SpareTimeExecuteTypes.Serial)]
public void DoSomething1(SpareTimer timer, long count)
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine($"一共执行了:{count} 次");
}
26.10 任务异常处理
有些时候我们可能在执行任务过程中出现异常,Furion
也提供了属性判断是否有异常和异常信息,方便记录到日志中,如:
SpareTime.Do(1000, (t, c) =>
{
// 判断是否有异常
if (t.Exception.Any())
{
Console.WriteLine(t.Exception.Values.LastOrDefault()?.Message);
}
// 执行第三次抛异常
if (c > 2)
{
throw Oops.Oh("抛异常" + c);
}
else
{
Console.WriteLine($"{t.WorkerName} -{t.Description} - {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {c}");
}
}, "exceptionJob");
特别说明
如果一个任务错误次数达 10次
则任务将自动停止,并标记任务状态为 Failed
。
26.11 如何在任务中解析对象
有些时候我们需要在任务中进行数据库操作或解析服务,这时候我们只需要创建一个新的作用域即可
26.11.1 SpareTime
静态类中
SpareTime.Do(1000, (timer,count) => {
Scoped.Create((_, scope) =>
{
var services = scope.ServiceProvider;
// 获取数据库上下文
var dbContext = Db.GetDbContext(services);
// 获取仓储
var respository = Db.GetRepository<Person>(services);
// 解析其他服务
var otherService = services.GetService<XXX>();
var otherService2 = App.GetService<XXX>(services);
});
}, "任务标识");
26.11.2 ISpareTimeWorker
方式
[SpareTime(1000, "jobName1", StartNow = true, ExecuteType = SpareTimeExecuteTypes.Serial)]
public void DoSomething1(SpareTimer timer, long count)
{
Scoped.Create((_, scope) =>
{
var services = scope.ServiceProvider;
// 获取数据库上下文
var dbContext = Db.GetDbContext(services);
// 获取仓储
var respository = Db.GetRepository<Person>(services);
// 解析其他服务
var otherService = services.GetService<XXX>();
var otherService2 = App.GetService<XXX>(services);
});
}
数据库操作注意
如果作用域中对数据库有任何变更操作,需手动调用 SaveChanges
或带 Now
结尾的方法。也可以使用 Scoped.CreateUow(handler)
代替。
26.12 在 BackgroundService
中使用
BackgroundService
是 .NET Core 3
之后提供的轻量级后台任务,同时可以发布到 Windows
服务和 Linux
守护进程中。
26.12.1 间隔执行方式
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WorkerService1
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 间隔执行任务
await SpareTime.DoAsync(1000, () =>
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}, stoppingToken);
}
}
}
}
26.12.2 Cron
表达式执行方式
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WorkerService1
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 执行 Cron 表达式任务
await SpareTime.DoAsync("*/5 * * * * *", () =>
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}, stoppingToken, CronFormat.IncludeSeconds);
}
}
}
}
26.13 定时任务监听器
在 Furion v2.18+
版本之后新增了定时任务监听器 ISpareTimeListener
,通过监听器可以实现所有定时任务的状态。如,创建一个 单例
的监听器 SpareTimeListener
:
using Furion.DependencyInjection;
using Furion.TaskScheduler;
using System;
using System.Threading.Tasks;
namespace Furion.Core
{
public class SpareTimeListener : ISpareTimeListener, ISingleton
{
/// <summary>
/// 监听所有任务
/// </summary>
/// <param name="executer"></param>
/// <returns></returns>
public Task OnListener(SpareTimerExecuter executer)
{
switch (executer.Status)
{
// 执行开始通知
case 0:
Console.WriteLine($"{executer.Timer.WorkerName} 任务开始通知");
break;
// 任务执行之前通知
case 1:
Console.WriteLine($"{executer.Timer.WorkerName} 执行之前通知");
break;
// 执行成功通知
case 2:
Console.WriteLine($"{executer.Timer.WorkerName} 执行成功通知");
break;
// 任务执行失败通知
case 3:
Console.WriteLine($"{executer.Timer.WorkerName} 执行失败通知");
break;
// 任务执行停止通知
case -1:
Console.WriteLine($"{executer.Timer.WorkerName} 执行停止通知");
break;
// 任务执行取消通知
case -2:
Console.WriteLine($"{executer.Timer.WorkerName} 执行取消通知");
break;
default:
break;
}
return Task.CompletedTask;
}
}
}
26.13.1 SpareTimerExecuter
属性说明
Timer
:SpareTimer
定时器对象Status
:监听状态0
:任务开始1
:执行之前2
:执行成功3
:执行失败-1
:任务停止-2
:任务取消
26.14 IIS 部署回收设置
如果在项目中使用了定时任务且部署到 IIS
中,那么需要设置 IIS
禁止回收,如:
部署建议
建议定时任务采用 Worker Service
独立部署方式,不应依托 Web
项目进程中。查看【 Worker Service】章节
26.15 反馈与建议
与我们交流
给 Furion 提 Issue。