【X23】 04 定时任务(Hangfire)
前言
知识点:
Hangfire官网
目的: 利用 Hangfire 生成可视化启停定时任务
1 简单介绍
在 .NET 和 .NET Core 应用程序中执行后台处理的简单方法。无需 Windows 服务或单独的进程。由持久存储支持。开放且免费用于商业用途。更多介绍...
2 如何完成一个用户可视化的定时任务看板
简单的定时任务通过 Hangfire
执行根据官网即可实现。基本使用方法暂不描述,官网已经十分十分详细了!在此主要解决怎么处理生成一个可视化的启停定时任务。当然,Hangfire自带看板,但那应该主要面对于开发等相关人员,用户往往只需要对一个任务看板进行任务的查看、启停以及让他按照什么样的规律去处理任务。
通过翻阅官网发现,执行周期任务只需要
RecurringJob.AddOrUpdate(() => Console.Write("Easy!"), Cron.Daily);
此时发现,如果我有很多定时任务则需要写很多 RecurringJob.AddOrUpdate
方法,导致很多重复性的代码,比如更改cron表达式,启停任务等。
设想,我们只写主要的定时任务所需要的 处理方法
,对该方法标识为定时任务,则可以通过反射得到本程序的所有 定时任务。结合用户已定义的Cron表达式和启停操作,最终构造出用户可视化的定时任务看板。
3 用户可视化的定时任务看板实现
3.1 构建JobAttribute,标识方法为定时任务方法
/// <summary>
/// 定时任务
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class JobAttribute : Attribute
{
/// <summary>
/// 定时任务
/// </summary>
/// <param name="key">关键字</param>
/// <param name="methtod">方法名称</param>
/// <param name="cron">Cron表达式</param>
/// <param name="jobName">任务名称</param>
/// <param name="cronName">Cron表达式名称</param>
public JobAttribute(string key, string methtod, string cron, string jobName, string cronName)
{
Key = key;
Methtod = methtod;
Cron = cron;
JobName = jobName;
CronName = cronName;
}
// ...省略
}
3.2 获取所有的定时任务
首先通过JobAttribute可以获取到所有的定时任务。此处采用静态字典存储。
internal static class StaticDic
{
/// <summary>
/// 全局静态变量
/// </summary>
internal static Dictionary<string, JobAttributeAndTypeDto> JobStaticDic = new();
/// <summary>
/// 初始化注册JobAttribute特性
/// </summary>
/// <param name="assemblies"></param>
internal static void IniJobStaticDic(params Assembly[] assemblies)
{
var jobAttributeAndTypeDtoList = new List<JobAttributeAndTypeDto>();
assemblies.ToList().ForEach(item =>
{
var attributes = item.DefinedTypes.SelectMany(x => x.DeclaredMethods
.Where(x => x.GetCustomAttribute(typeof(JobAttribute), false) != null))
.Select(x => new JobAttributeAndTypeDto
{
JobAttribute = x.GetCustomAttribute(typeof(JobAttribute), false) as JobAttribute,
ClassType = x.DeclaringType
})
.ToList();
jobAttributeAndTypeDtoList.AddRange(attributes);
});
JobStaticDic = jobAttributeAndTypeDtoList.ToDictionary(
x => x.JobAttribute.Key,
y => new JobAttributeAndTypeDto
{
ClassType = y.ClassType,
JobAttribute = y.JobAttribute
});
}
}
其次,通过Hangfire存储的Set
以及Hash
表可以查询关联得到定时任务的状态
select * from `Set`;
select * from `Hash`;
-- 查询Hangfire定时任务
SELECT tSet.`Key`,tHash.`Json`
FROM (SELECT `Value` AS `Key`,CONCAT("recurring-job",':',`Value`) AS `SetKey` FROM `Set`) tSet
LEFT JOIN
(
SELECT `Key` AS `HashKey`,CONCAT('{',GROUP_CONCAT('"',`Field`,'":"',`Value`,'"'),'}') AS `Json`
FROM `Hash`
WHERE `Field` IN ('CreatedAt','NextExecution','LastExecution')
GROUP BY `Key`
) `tHash`
ON tSet.SetKey = tHash.HashKey
定时任务关键字 | 任务描述Json(创建时间、上次执行时间、下次执行时间) |
---|---|
TestJobKey |
此时,即可关联得到任务看板!
/// <summary>
/// 获取所有的job
/// </summary>
/// <returns></returns>
public List<AllJobDto> GetAllJob()
{
var hanfireHashKeySetList = _dbConnection.Query<HanfireHashKeySet>(
@"
SELECT tSet.`Key`,tHash.`Json`
FROM (SELECT `Value` AS `Key`,CONCAT(""recurring-job"",':',`Value`) AS `SetKey` FROM `Set`) tSet
LEFT JOIN
(
SELECT `Key` AS `HashKey`, CONCAT('{', GROUP_CONCAT('""',`Field`, '"":""',`Value`, '""'), '}') AS `Json`
FROM `Hash`
WHERE `Field` IN('CreatedAt', 'NextExecution', 'LastExecution')
GROUP BY `Key`
) `tHash`
ON tSet.SetKey = tHash.HashKey
"
).ToList();
var dic = StaticDic.JobStaticDic.Select(x => x.Value.JobAttribute).OrderBy(x => x.Key).ToList();
var result = from a in dic
join b in hanfireHashKeySetList
on a.Key equals b.Key
into temp
from tt in temp.DefaultIfEmpty()
select new AllJobDto
{
Key = a.Key,
Cron = a.Cron,
CronName = a.CronName,
JobName = a.JobName,
Methtod = a.Methtod,
JobStatus = tt == null ? JobStatus.No : JobStatus.Executing,
Json = tt == null ? "{}" : tt.Json
};
return result.ToList();
}
3.3 启停定时任务
停止任务很简单,通过Hangfire自带Api RecurringJob.Trigger(key);
即可。那开始运行定时任务怎么处理呢?
前面我们用 JobStaticDic
去存储了所有的任务方法、key、Cron表达式等。故而实现入下:
/// <summary>
/// 运行周期任务
/// </summary>
/// <param name="key">指定标识符</param>
/// <returns></returns>
public bool RunJob(string key)
{
RecurringJob.AddOrUpdate(key, () => DoWork(key), StaticDic.JobStaticDic[key].JobAttribute.Cron);
return true;
}
/// <summary>
/// 运行周期任务
/// </summary>
/// <param name="key">指定标识符</param>
/// <returns></returns>
public async Task<bool> DoWork(string key)
{
var jobAttributeDto = StaticDic.JobStaticDic[key];
//C#反射调用外部Dll,执行其中异步函数并取返回值
//https://www.cnblogs.com/cteng-common/p/9011920.html
var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, jobAttributeDto.ClassType);
var data = jobAttributeDto.ClassType.GetMethod(jobAttributeDto.JobAttribute.Methtod).Invoke(obj, null);
if (data is Task)
{
var task = data as Task;
await task;
}
return true;
}