软件项目中有的时候会需要一些计划执行任务,比如每周定时清理日志、每月给客户发送电子邮件或者是自动每天结算用户的收益等等,而这些重复且对时间有要求的事情我们肯定是不愿意每天或固定时间去手动操作的,所以我们需要考虑到使用程序来自动运行了。对于计划任务目前比较常见的方式有:Windows服务、Web线程轮训、数据库作业等等,其实对于他们的本质也都是一样的通过线程或者是进程来循环检查,只是使用场景和方法不一样而已。
最近在研究NopCommerce这个开源项目,所以我分享下他的计划任务是如何实现的。
注意:因为NopCommerce项目结构比较大和复杂,所以我这里针对计划任务部分抽取了一个小Demo,下面的讲解我将通过Demo来说明,点击下载完整Demo代码
一、所涉及到表结构
定时任务通过一张表来完成的,表明:ScheduleTask,里面的字段和说明请查看下面的完整代码,其中LeasedByMachineName 和 LeasedUntilUtc 暂不清除使用
1 |
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 |
/// <summary> ///计划任务 数据库实体 /// </summary> public partial class ScheduleTask { public int Id { get; set; } /// <summary> ///计划任务的名称 /// </summary> public string Name { get; set; } /// <summary> /// 任务间隔时长(秒) /// </summary> public int Seconds { get; set; } /// <summary> ///执行任务的类所对应的程序集和命名空间的字符串,用于反射创建类 /// </summary> public string Type { get; set; } /// <summary> /// 是否启用任务 /// </summary> public bool Enabled { get; set; } /// <summary> /// 发送错误是否停止执行 /// </summary> public bool StopOnError { get; set; } /// <summary> /// 计算机的名称,这个好像是多网站(分布式)的时候有用 (具体的使用暂不清楚) /// </summary> public string LeasedByMachineName { get; set; } /// <summary> /// 实例上执行的时间 (具体的使用暂不清楚) /// </summary> public DateTime? LeasedUntilUtc { get; set; } /// <summary> /// 最后开始执行任务的开始时间 /// </summary> public DateTime? LastStartUtc { get; set; } /// <summary> /// 后开始执行任务的结束时间 /// </summary> public DateTime? LastEndUtc { get; set; } /// <summary> ///最后一次执行成功的时间 /// </summary> public DateTime? LastSuccessUtc { get; set; } } |
二、所涉及到的类说明
这里总共有6个类完成计划任务的功能。
详细解释:
IScheduleTaskService 是相关数据库操作的服务接口,比如添加、编辑、查询、删除等等。
ScheduleTaskService 是IScheduleTaskService的实现类,完成对数据库的相关操作。
ITask 计划任务的接口,所有需要计划执行任务的类都应该继承该类,里面有个Execute(),而实现类需要实现该类完成需要具体执行的任务 。
TaskManager 是控制所有的计划任务的执行、结束、初始化等等操作。
TaskThread 是任务线程,每个任务都会产生一个线程,以方便任务能独立运行。
Task 任务,记录任务执行的部分数据和通过反射来产生具体需要执行的任务。
三、计划任务的流程
1、创建具体的任务类(也就是需要自动执行任务的具体代码),并且需要继承ITask类,然后在Execute()函数里面写相关代码。
这里我随便创建了一个类,为减少代码所以这里就调用IO写文本内容了。 注意日志文件的路径:D:\Test\log.txt,请选创建文件夹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class WriteLog : ITask { private static string filePath = @"D:\Test\log.txt"; public void Execute() { string content = " WriteLog:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n"; FileInfo fs = new FileInfo(filePath); StreamWriter sw = fs.AppendText(); sw.WriteLine(content); sw.Flush(); sw.Close(); } } |
为了显示查看效果,我这里创建2个任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class WriteLog2 : ITask { private static string filePath = @"D:\Test\log.txt"; public void Execute() { string content = " WriteLog2:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n"; FileInfo fs = new FileInfo(filePath); StreamWriter sw = fs.AppendText(); sw.WriteLine(content); sw.Flush(); sw.Close(); } } |
2、数据库插入任务数据。
将任务相关数据插入到数据库中(下图)
我这里有2个计划任务,写日志1和写日志2执行间隔的时间分别是10秒和15秒,Type是类的命名空间加类名和所属项目的名称,因为需要通过反射来创建对象。其他的时间可为空,因为还未执行,执行任务以后就会发现相关时间有修改了。
3、全局注册计划任务和初始化
在Global.asax的Application_Start()函数里面,添加一下代码
1 2 3 4 |
//初始化计划任务 TaskManager.Instance.Initialize(); //开始执行 TaskManager.Instance.Start(); |
初始化代码是获取数据库中的所有计划任务,并根据任务同时产生线程,然后将线程存放到TaskManager的线程集合中。
开始执行是将线程里面的所有计划任务启动,并通过Timer控件来执行。
有关如何初始化和开启线程,我这里就不多做介绍了,大家看看源代码我想都能理解了。
运行结果(下图)
Log2是每个10秒运行一次, Log 是每个15秒运行一次
以上是我研究NOP中计划任务的一点总结,不全或不正之处望见谅,谢谢!
发布者:柚子,转转请注明出处:https://ityouzi.com/archives/nop-commerce-schedule-task.html