web下计划任务的实现方式
目录
一、前言
前两天在QQ群里大家又讨论起了在web下怎么做任务?大部分的群友提供的思路都是做windows服务。那么除了做windows服务以外,还有没有其他的思路或者解决方案呢?
今天和大家分享的是我在开源代码里看到的一部分关于web下实现任务的核心代码,在此抛砖引玉。
使用的技术和框架 : NetFramework4.5、C#、MVC4、log4net
二、解决思路
Net中提供的计时器中有两种:阻塞式定时器和非阻塞式定时器,在web中要达到定时完成某项任务,就需要用到非阻塞式的定时器,net提供的命名空间是:System.Threading,类是Timer,定时器会根据指定的间隔时间,定时指定任务代码。
三、核心类图UML和依赖关系
类图:
ITask是任务接口,定义类实行方法,是整个模块的核心接口
ScheduleTask是任务计划类,提供需要做计划任务的数据,ScheduleTaskList是提供任务计划的数据源,真实环境是需要数据库提供数据源
ITaskFactory是任务计划的工厂接口,TaskFactory继承ITaskFactory,根据提供的任务类型创建具体的任务
Task是任务执行结果类,实现了任务的运行和运行之后的状态数据如何保存(Demo中并为实现)
TaskThread是核心类,主要是定义定时器和维护任务列表,
TaskManager是核心类,主要是管理任务,提供初始化和开始方法,维护TaskThread列表
引用关系图:
LogTask实现ITask接口
ITaskFactory、TaskFactory、Task依赖ITask接口
TaskFactory实现ITaskFactory,依赖LogTask
Task依赖ITask、ITaskFactory、TaskFactory、ScheduleTask、ScheduleTaskList
ScheduleTaskList依赖SecheduleTask
TaskThread依赖Task
TaskManager依赖ScheduleTask、ScheduleTaskList、Task、TaskThread
四、源代码说明:
ITask:任务接口

1 /// <summary> 2 /// Interface that should be implemented by each task 3 /// </summary> 4 public interface ITask 5 { 6 #region Methods 7 8 /// <summary> 9 /// Execute task 10 /// </summary> 11 void Execute(); 12 13 #endregion 14 }
说明:声明了任务运行方法
ScheduleTask类:计划任务类

1 /// <summary> 2 /// The schedule task. 3 /// 计划任务,这里可以作为数据库的Model,将需要执行的任务保存到数据库 4 /// </summary> 5 public class ScheduleTask 6 { 7 #region Properties 8 9 /// <summary> 10 /// Gets or sets the name 11 /// 任务名称 12 /// </summary> 13 public string Name { get; set; } 14 15 /// <summary> 16 /// Gets or sets the run period (in seconds) 17 /// 执行间隔时间,单位毫秒 18 /// </summary> 19 public int Seconds { get; set; } 20 21 /// <summary> 22 /// Gets or sets the type of appropriate ITask class 23 /// 任务类型:这里如果采用IOC,保存到数据库的格式是:namespace,程序集.dll 24 /// </summary> 25 public string Type { get; set; } 26 27 /// <summary> 28 /// Gets or sets a value indicating whether enabled. 29 /// 任务是否启用 30 /// </summary> 31 public bool Enabled { get; set; } 32 33 /// <summary> 34 /// Gets or sets a value indicating whether stop on error. 35 /// </summary> 36 public bool StopOnError { get; set; } 37 38 /// <summary> 39 /// Gets or sets the last start. 40 /// 最后开始时间 41 /// </summary> 42 public DateTime? LastStart { get; set; } 43 44 /// <summary> 45 /// Gets or sets the last end. 46 /// 最后结束时间 47 /// </summary> 48 public DateTime? LastEnd { get; set; } 49 50 /// <summary> 51 /// Gets or sets the last success. 52 /// 最后执行成功时间 53 /// </summary> 54 public DateTime? LastSuccess { get; set; } 55 56 #endregion 57 }
说明:主要包含了几个属性
ScheduleTaskList:提供计划任务数据源

1 /// <summary> 2 /// The schedule task list. 3 /// </summary> 4 public class ScheduleTaskList : List<ScheduleTask> 5 { 6 /// <summary> 7 /// Prevents a default instance of the <see cref="ScheduleTaskList"/> class from being created. 8 /// </summary> 9 private ScheduleTaskList() 10 { 11 this.Add(new ScheduleTask 12 { 13 Enabled = true, 14 Name = "定时记录日志", 15 Seconds = 30 * 1000, 16 Type = "logTask" 17 }); 18 } 19 20 /// <summary> 21 /// The get schedule task by type. 22 /// </summary> 23 /// <param name="taskType"> 24 /// The task type. 25 /// </param> 26 /// <returns> 27 /// The <see cref="ScheduleTask"/>. 28 /// </returns> 29 public static ScheduleTask GetScheduleTaskByType(string taskType) 30 { 31 return new ScheduleTaskList().SingleOrDefault(u => u.Type == taskType); 32 } 33 34 /// <summary> 35 /// The get all schedule task. 36 /// </summary> 37 /// <returns> 38 /// The <see> 39 /// <cref>IEnumerable</cref> 40 /// </see> 41 /// . 42 /// </returns> 43 public static IEnumerable<ScheduleTask> GetAllScheduleTask() 44 { 45 return new ScheduleTaskList(); 46 } 47 }
说明:提供数据源,GetSchedulaTask是根据任务类型获取到计划任务,GetAllScheduleTask是获取所有的计划任务,这里主要为任务管理器提供所有的计划任务
ITaskFactory:Task工厂接口

1 /// <summary> 2 /// The TaskFactorycs interface. 3 /// 任务工厂,即使使用可以使用IOC替换,不需要使用工厂类 4 /// </summary> 5 public interface ITaskFactory 6 { 7 /// <summary> 8 /// The create task. 9 /// </summary> 10 /// <param name="taskType"> 11 /// The task type. 12 /// </param> 13 /// <returns> 14 /// The <see cref="ITask"/>. 15 /// </returns> 16 ITask CreateTask(string taskType); 17 }
说明:提供CreateTask方法,根据task类型创建具体需要执行的任务
TaskFactory:任务工厂实现,主要实现ITaskFactory接口

1 /// <summary> 2 /// The task factory. 3 /// 任务工厂实现 4 /// </summary> 5 public class TaskFactory : ITaskFactory 6 { 7 /// <summary> 8 /// The create task. 9 /// </summary> 10 /// <param name="taskType"> 11 /// The task type. 12 /// </param> 13 /// <returns> 14 /// The <see cref="ITask"/>. 15 /// </returns> 16 public ITask CreateTask(string taskType) 17 { 18 if (taskType == "logTask") 19 { 20 return new LogTask(); 21 } 22 23 return null; 24 } 25 }
说明:这个工厂是真实环境中替代IOC的,此处主要创建了LogTask
LogTask:具体需要执行的任务,这里调用了Log4Net 记录日志

1 /// <summary> 2 /// The log task. 3 /// </summary> 4 public class LogTask : ITask 5 { 6 #region ITask 7 8 /// <summary> 9 /// The execute. 10 /// </summary> 11 public void Execute() 12 { 13 // TODO:具体任务干的事情 14 MvcApplication.Log().Info("具体任务做的事情"); 15 } 16 17 #endregion 18 }
说明:这个类就是具体要执行的任务了,任务执行的具体内容都卸载了Execute方法内
Task:任务类

1 /// <summary> 2 /// Task 3 /// 任务类 4 /// </summary> 5 public class Task 6 { 7 #region Ctor 8 9 /// <summary> 10 /// Initializes a new instance of the <see cref="Task"/> class. 11 /// </summary> 12 /// <param name="task"> 13 /// The task. 14 /// </param> 15 public Task(ScheduleTask task) 16 { 17 this.Type = task.Type; 18 this.Enabled = task.Enabled; 19 this.StopOnError = task.StopOnError; 20 this.Name = task.Name; 21 } 22 23 24 /// <summary> 25 /// Prevents a default instance of the <see cref="Task"/> class from being created. 26 /// </summary> 27 private Task() 28 { 29 this.Enabled = true; 30 } 31 32 33 #endregion 34 35 #region Properties 36 37 /// <summary> 38 /// 是否正在运行 39 /// </summary> 40 public bool IsRunning { get; private set; } 41 42 /// <summary> 43 /// 任务最后开始时间 44 /// </summary> 45 public DateTime? LastStart { get; private set; } 46 47 /// <summary> 48 /// 任务最后结束时间 49 /// </summary> 50 public DateTime? LastEnd { get; private set; } 51 52 /// <summary> 53 /// 任务最后执行成功时间 54 /// </summary> 55 public DateTime? LastSuccess { get; private set; } 56 57 /// <summary> 58 /// 任务类型 59 /// </summary> 60 public string Type { get; private set; } 61 62 /// <summary> 63 /// 是否因为发生错误而停止 64 /// </summary> 65 public bool StopOnError { get; private set; } 66 67 /// <summary> 68 /// 任务名称 69 /// </summary> 70 public string Name { get; private set; } 71 72 /// <summary> 73 /// 是否启用 74 /// </summary> 75 public bool Enabled { get; set; } 76 77 #endregion 78 79 80 #region Methods 81 82 /// <summary> 83 /// The execute. 84 /// 执行任务 85 /// </summary> 86 /// <param name="throwException"> 87 /// The throw exception. 88 /// </param> 89 /// <param name="dispose"> 90 /// The dispose. 91 /// </param> 92 public void Execute(bool throwException = false, bool dispose = true) 93 { 94 this.IsRunning = true; 95 96 var scheduleTask = ScheduleTaskList.GetScheduleTaskByType(this.Type); 97 98 try 99 { 100 ITask task = this.CreateTask(this.Type, new TaskFactory()); 101 102 if (task != null) 103 { 104 this.LastStart = DateTime.Now; 105 if (scheduleTask != null) 106 { 107 // update appropriate datetime properties 108 scheduleTask.LastStart = this.LastStart; 109 // 更新任务计划执行情况 110 //scheduleTaskService.Update(scheduleTask); 111 } 112 113 task.Execute(); 114 this.LastEnd = this.LastSuccess = DateTime.UtcNow; 115 } 116 } 117 catch (Exception exc) 118 { 119 this.Enabled = !this.StopOnError; 120 this.LastEnd = DateTime.UtcNow; 121 } 122 123 if (scheduleTask != null) 124 { 125 scheduleTask.LastEnd = this.LastEnd; 126 scheduleTask.LastSuccess = this.LastSuccess; 127 // 更新任务计划执行情况 128 //scheduleTaskService.Update(scheduleTask); 129 } 130 131 this.IsRunning = false; 132 } 133 134 /// <summary> 135 /// The create task. 136 /// </summary> 137 /// <param name="taskType"> 138 /// The task type. 139 /// </param> 140 /// <param name="taskFactory"> 141 /// The task factory. 142 /// </param> 143 /// <returns> 144 /// The <see cref="ITask"/>. 145 /// </returns> 146 /// <exception cref="ArgumentNullException"> 147 /// </exception> 148 private ITask CreateTask(string taskType, ITaskFactory taskFactory) 149 { 150 if (taskFactory == null) 151 { 152 throw new ArgumentNullException("taskFactory"); 153 } 154 155 ITask task = null; 156 if (this.Enabled) 157 { 158 // Demo中采用了任务工厂的方式,实际过程中采用IOC 159 task = taskFactory.CreateTask(taskType); 160 } 161 162 return task; 163 } 164 165 #endregion 166 }
说明:这里主要是获取到具体的任务,然后执行任务,并且把任务执行结果保存起来
TaskThread:任务线程类

1 /// <summary> 2 /// Represents task thread 3 /// </summary> 4 public class TaskThread : IDisposable 5 { 6 #region Fields 7 8 /// <summary> 9 /// The timer. 10 /// 定时器 11 /// </summary> 12 private Timer timer; 13 14 /// <summary> 15 /// The disposed. 16 /// 知否已销毁 17 /// </summary> 18 private bool disposed; 19 20 /// <summary> 21 /// The tasks. 22 /// 任务字典 23 /// </summary> 24 private readonly Dictionary<string, Task> tasks; 25 26 #endregion 27 28 #region Ctor 29 30 /// <summary> 31 /// Initializes a new instance of the <see cref="TaskThread"/> class. 32 /// </summary> 33 internal TaskThread() 34 { 35 this.tasks = new Dictionary<string, Task>(); 36 this.Seconds = 10 * 60; 37 } 38 39 #endregion 40 41 42 #region Peoperties 43 44 /// <summary> 45 /// Gets or sets the interval in seconds at which to run the tasks 46 /// </summary> 47 public int Seconds { get; set; } 48 49 /// <summary> 50 /// Get or sets a datetime when thread has been started 51 /// </summary> 52 public DateTime Started { get; private set; } 53 54 /// <summary> 55 /// Get or sets a value indicating whether thread is running 56 /// </summary> 57 public bool IsRunning { get; private set; } 58 59 /// <summary> 60 /// Get a list of tasks 61 /// </summary> 62 public IList<Task> Tasks 63 { 64 get 65 { 66 var list = new List<Task>(); 67 foreach (var task in this.tasks.Values) 68 { 69 list.Add(task); 70 } 71 72 return new ReadOnlyCollection<Task>(list); 73 } 74 } 75 76 /// <summary> 77 /// Gets the interval at which to run the tasks 78 /// </summary> 79 public int Interval 80 { 81 get 82 { 83 return this.Seconds; 84 } 85 } 86 87 /// <summary> 88 /// Gets or sets a value indicating whether the thread whould be run only once (per appliction start) 89 /// </summary> 90 public bool RunOnlyOnce { get; set; } 91 92 #endregion 93 94 95 96 #region IDisposable 97 98 /// <summary> 99 /// Disposes the instance 100 /// </summary> 101 public void Dispose() 102 { 103 if ((this.timer == null) || this.disposed) 104 { 105 return; 106 } 107 108 lock (this) 109 { 110 this.timer.Dispose(); 111 this.timer = null; 112 this.disposed = true; 113 } 114 } 115 116 #endregion 117 118 119 /// <summary> 120 /// Inits a timer 121 /// 初始化定时器 122 /// </summary> 123 public void InitTimer() 124 { 125 if (this.timer == null) 126 { 127 this.timer = new Timer(new TimerCallback(this.TimerHandler), null, this.Interval, this.Interval); 128 } 129 } 130 131 132 /// <summary> 133 /// Adds a task to the thread 134 /// </summary> 135 /// <param name="task"> 136 /// The task to be added 137 /// </param> 138 public void AddTask(Task task) 139 { 140 if (!this.tasks.ContainsKey(task.Name)) 141 { 142 this.tasks.Add(task.Name, task); 143 } 144 } 145 146 /// <summary> 147 /// The run. 148 /// </summary> 149 private void Run() 150 { 151 if (this.Seconds <= 0) 152 { 153 return; 154 } 155 156 this.Started = DateTime.Now; 157 this.IsRunning = true; 158 foreach (Task task in this.tasks.Values) 159 { 160 task.Execute(); 161 } 162 163 this.IsRunning = false; 164 } 165 166 /// <summary> 167 /// The timer handler. 168 /// </summary> 169 /// <param name="state"> 170 /// The state. 171 /// </param> 172 private void TimerHandler(object state) 173 { 174 this.timer.Change(-1, -1); 175 this.Run(); 176 if (this.RunOnlyOnce) 177 { 178 this.Dispose(); 179 } 180 else 181 { 182 this.timer.Change(this.Interval, this.Interval); 183 } 184 } 185 }
说明:这个类主要是靠Timer定时器来实现的,初始化定时器以及需要执行的任务,
TaskManager:任务计划的管理器

1 /// <summary> 2 /// Represents task manager 3 /// </summary> 4 public class TaskManager 5 { 6 #region Fields 7 8 /// <summary> 9 /// The task manager. 10 /// </summary> 11 private static readonly TaskManager taskManager = new TaskManager(); 12 13 /// <summary> 14 /// The _task threads. 15 /// </summary> 16 private readonly List<TaskThread> taskThreads = new List<TaskThread>(); 17 18 /// <summary> 19 /// The _not run tasks interval. 20 /// </summary> 21 private const int NotRunTasksInterval = 60 * 30; // 30 minutes 22 23 #endregion 24 25 #region Ctor 26 27 /// <summary> 28 /// Prevents a default instance of the <see cref="TaskManager"/> class from being created. 29 /// </summary> 30 private TaskManager() 31 { 32 } 33 34 #endregion 35 36 #region Properties 37 38 /// <summary> 39 /// Gets the task mamanger instance 40 /// </summary> 41 public static TaskManager Instance 42 { 43 get 44 { 45 return taskManager; 46 } 47 } 48 49 /// <summary> 50 /// Gets a list of task threads of this task manager 51 /// 任务线程列表 52 /// </summary> 53 public IList<TaskThread> TaskThreads 54 { 55 get 56 { 57 return new ReadOnlyCollection<TaskThread>(this.taskThreads); 58 } 59 } 60 61 62 #endregion 63 64 #region Methods 65 66 /// <summary> 67 /// Initializes the task manager with the property values specified in the configuration file. 68 /// 这里读取所有的计划任务,并对计划任务按照运行时间间隔分组,每组的任务创建一个TaskThread 69 /// </summary> 70 public void Initialize() 71 { 72 this.taskThreads.Clear(); 73 74 // 按运行时间间隔分组 75 var scheduleTasks = ScheduleTaskList.GetAllScheduleTask().OrderBy(x => x.Seconds).ToList(); 76 77 // group by threads with the same seconds 78 foreach (var scheduleTaskGrouped in scheduleTasks.GroupBy(x => x.Seconds)) 79 { 80 // create a thread 81 var taskThread = new TaskThread { Seconds = scheduleTaskGrouped.Key }; 82 foreach (var scheduleTask in scheduleTaskGrouped) 83 { 84 var schedTask = new ScheduleTask 85 { 86 Type = scheduleTask.Type, 87 StopOnError = scheduleTask.StopOnError, 88 Seconds = scheduleTask.Seconds, 89 Name = scheduleTask.Name, 90 LastSuccess = scheduleTask.LastSuccess, 91 LastStart = scheduleTask.LastStart, 92 LastEnd = scheduleTask.LastEnd, 93 Enabled = scheduleTask.Enabled, 94 }; 95 var task = new Task(schedTask); 96 taskThread.AddTask(task); 97 } 98 99 this.taskThreads.Add(taskThread); 100 } 101 102 // sometimes a task period could be set to several hours (or even days). 103 // in this case a probability that it'll be run is quite small (an application could be restarted) 104 // we should manually run the tasks which weren't run for a long time 105 var notRunTasks = scheduleTasks 106 107 // find tasks with "run period" more than 30 minutes 108 .Where(x => x.Seconds >= NotRunTasksInterval) 109 .Where(x => !x.LastStart.HasValue || x.LastStart.Value.AddSeconds(x.Seconds) < DateTime.Now) 110 .ToList(); 111 112 // create a thread for the tasks which weren't run for a long time 113 if (notRunTasks.Count > 0) 114 { 115 var taskThread = new TaskThread 116 { 117 RunOnlyOnce = true, 118 Seconds = 60 * 5 119 // let's run such tasks in 5 minutes after application start 120 }; 121 foreach (var scheduleTask in notRunTasks) 122 { 123 var schedTask = new ScheduleTask 124 { 125 Type = scheduleTask.Type, 126 StopOnError = scheduleTask.StopOnError, 127 Seconds = scheduleTask.Seconds, 128 Name = scheduleTask.Name, 129 LastSuccess = scheduleTask.LastSuccess, 130 LastStart = scheduleTask.LastStart, 131 LastEnd = scheduleTask.LastEnd, 132 Enabled = scheduleTask.Enabled, 133 }; 134 var task = new Task(schedTask); 135 taskThread.AddTask(task); 136 } 137 138 this.taskThreads.Add(taskThread); 139 } 140 } 141 142 /// <summary> 143 /// Starts the task manager 144 /// </summary> 145 public void Start() 146 { 147 foreach (var taskThread in this.taskThreads) 148 { 149 taskThread.InitTimer(); 150 } 151 } 152 153 /// <summary> 154 /// Stops the task manager 155 /// </summary> 156 public void Stop() 157 { 158 foreach (var taskThread in this.taskThreads) 159 { 160 taskThread.Dispose(); 161 } 162 } 163 164 #endregion 165 }
说明:负责初始化所有的计划任务,并且对计划任务进行分组,提供任务计划的开始和停止,维护TaksThread列表
使用代码:
TaskManager.Instance.Initialize();// 初始化任务
TaskManager.Instance.Start(); // 开始任务
五、运行结果
根据上图可以看到,任务正常执行,并且是每隔30s记录一次日志,说明了上面的代码是正常的,也说明了采用定时器的方案,可以实现web下定义和执行计划任务的
六、总结
这篇文章主要是提供了web下实现计划任务的一种解决思路和具体方案,不在需要写windows服务,通过自定义任务管理器和任务线程达到定时执行某项任务的功能。
具体可以使用的场景有:异步发送邮件、异步发送短信等
源码:下载地址:http://share.weiyun.com/d6140bfa1b3ce4e968f58c5335704551
技术交流:351157970 (QQ) 59557329(QQ群:C#基地)