阅读:        作者:Charisma.

在mvc项目中写一个线程的定时器(计划任务)

因为看见nop中有计划任务Task,是用于定时发送邮件、清理缓存、获取汇率、清理日志、删除访客记录,最近不是很忙,就把这块剥离了出来,以后如果用得到就可以直接拿来用。废话说到这里。下面上代码:

一个计划任务,主要包括这几个文件。

IScheduleTaskService是任务表的一些操作。

ITask是任务接口,定义类实行方法,是整个模块的核心接口

ITaskService是执行的任务接口,实现具体要做的任务

ScheduleTask是任务计划类,提供需要做计划任务的数据

Task是任务执行结果类,实现了任务的运行和运行之后的状态数据如何保存

TaskManager是核心类,主要是管理任务,提供初始化和开始方法,维护TaskThread列表

TaskService是ITaskService的实现,实现具体的任务执行的方法

TaskThread是核心类,主要是定义定时器和维护任务列表

 

IScheduleTaskService:

public partial interface IScheduleTaskService {
        /// <summary>
        /// 删除任务
        /// </summary>
        /// <param name="task"></param>
        void DeleteTask(ScheduleTask task);

        /// <summary>
        /// 根据ID获取任务
        /// </summary>
        /// <param name="taskId">主键</param>
        /// <returns></returns>
        ScheduleTask GetTaskById(int taskId);

        /// <summary>
        /// 根据类型获取任务
        /// </summary>
        /// <param name="type">任务类型</param>
        /// <returns></returns>
        ScheduleTask GetTaskByType(string type);

        /// <summary>
        /// 获取所有任务
        /// </summary>
        /// <param name="showHidden">是否显示隐藏的任务</param>
        /// <returns>Tasks</returns>
        IList<ScheduleTask> GetAllTasks(bool showHidden = false);

        /// <summary>
        /// 增加一个任务
        /// </summary>
        /// <param name="task"></param>
        void InsertTask(ScheduleTask task);

        /// <summary>
        /// 更新一个任务
        /// </summary>
        /// <param name="task"></param>
        void UpdateTask(ScheduleTask task);
    }

ITask:

public partial interface ITask {
        /// <summary>
        /// 执行任务
        /// </summary>
        void Execute();
    }

ITaskService:

public interface ITaskService {
        ITask CreateTask(string taskType);
    }

ScheduleTaskService:

public partial class ScheduleTaskService : IScheduleTaskService {
        #region Fields

        private readonly IRepository<ScheduleTask> _taskRepository;

        #endregion

        #region Ctor

        public ScheduleTaskService(IRepository<ScheduleTask> taskRepository) {
            this._taskRepository = taskRepository;
        }

        #endregion

        #region Methods

        
        public virtual void DeleteTask(ScheduleTask task) {
            if (task == null)
                throw new ArgumentNullException("task");

            _taskRepository.Delete(task);
        }

       
        public virtual ScheduleTask GetTaskById(int taskId) {
            if (taskId == 0)
                return null;

            return _taskRepository.GetById(taskId);
        }

        
        public virtual ScheduleTask GetTaskByType(string type) {
            if (String.IsNullOrWhiteSpace(type))
                return null;

            var query = _taskRepository.Table;
            query = query.Where(st => st.Type == type);
            query = query.OrderByDescending(t => t.ID);

            var task = query.FirstOrDefault();
            return task;
        }

        
        public virtual IList<ScheduleTask> GetAllTasks(bool showHidden = false) {
            var query = _taskRepository.Table;
            if (!showHidden) {
                query = query.Where(t => t.Enabled);
            }
            query = query.OrderByDescending(t => t.Seconds);

            var tasks = query.ToList();
            return tasks;
        }

       
        public virtual void InsertTask(ScheduleTask task) {
            if (task == null)
                throw new ArgumentNullException("task");

            _taskRepository.Insert(task);
        }

        
        public virtual void UpdateTask(ScheduleTask task) {
            if (task == null)
                throw new ArgumentNullException("task");

            _taskRepository.Update(task);
        }

        #endregion
    }

Task:

public class Task {
        #region Ctor

        /// <summary>
        /// 初始化新实例
        /// </summary>
        /// <param name="task"></param>
        public Task(ScheduleTask task) {
            this.Type = task.Type;
            this.Enabled = task.Enabled;
            this.StopOnError = task.StopOnError;
            this.Name = task.Name;
        }


        /// <summary>
        /// 创建默认实例
        /// </summary>
        private Task() {
            this.Enabled = true;
        }


        #endregion

        #region Properties

        /// <summary>
        /// 是否正在运行
        /// </summary>
        public bool IsRunning { get; private set; }

        /// <summary>
        /// 任务最后开始时间
        /// </summary>
        public DateTime? LastStart { get; private set; }

        /// <summary>
        /// 任务最后结束时间
        /// </summary>
        public DateTime? LastEnd { get; private set; }

        /// <summary>
        /// 任务最后执行成功时间
        /// </summary>
        public DateTime? LastSuccess { get; private set; }

        /// <summary>
        /// 任务类型
        /// </summary>
        public string Type { get; private set; }

        /// <summary>
        /// 是否因为发生错误而停止
        /// </summary>
        public bool StopOnError { get; private set; }

        /// <summary>
        /// 任务名称
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// 是否启用
        /// </summary>
        public bool Enabled { get; set; }

        #endregion


        #region Methods

        /// <summary>
        /// The execute.
        /// 执行任务
        /// </summary>
        /// <param name="throwException">
        /// The throw exception.
        /// </param>
        /// <param name="dispose">
        /// The dispose.
        /// </param>
        public void Execute(bool throwException = false, bool dispose = true) {
            this.IsRunning = true;


            IRepository<ScheduleTask> _taskRepo = new EFRepository<ScheduleTask>();
            var query = _taskRepo.Table;
            query = query.Where(st => st.Type == this.Type);
            query = query.OrderByDescending(t => t.ID);
            var scheduleTask = query.FirstOrDefault();


            try {
                ITaskService taskService = new TaskService();
                ITask task = this.CreateTask(this.Type, taskService);

                if (task != null) {
                    this.LastStart = DateTime.Now;
                    if (scheduleTask != null) {
                        scheduleTask.LastStartDate = this.LastStart;
                        // 更新任务计划执行情况
                        //scheduleTaskService.Update(scheduleTask); 
                    }

                    task.Execute();
                    this.LastEnd = this.LastSuccess = DateTime.UtcNow;
                }
            }
            catch (Exception exc) {
                this.Enabled = !this.StopOnError;
                this.LastEnd = DateTime.UtcNow;
            }

            if (scheduleTask != null) {
                scheduleTask.LastEndDate = this.LastEnd;
                scheduleTask.LastSuccessDate = this.LastSuccess;
                // 更新任务计划执行情况
                //scheduleTaskService.Update(scheduleTask); 
            }

            this.IsRunning = false;
        }


        private ITask CreateTask(string taskType, ITaskService taskService) {
            if (taskService == null) {
                throw new ArgumentNullException("taskService");
            }

            ITask task = null;
            if (this.Enabled) {
                task = taskService.CreateTask(taskType);
            }
            return task;
        }

        #endregion
    }

TaskManager:

public class TaskManager {
        #region Fields

        /// <summary>
        /// 任务管理
        /// </summary>
        private static readonly TaskManager taskManager = new TaskManager();

        /// <summary>
        /// 任务线程
        /// </summary>
        private readonly List<TaskThread> taskThreads = new List<TaskThread>();

        

        /// <summary>
        /// 任务运行间隔时间
        /// </summary>
        private const int NotRunTasksInterval = 60 * 30; // 30 minutes

        #endregion

        #region Ctor

        /// <summary>
        /// 
        /// </summary>
        private TaskManager() {
        }

        #endregion

        #region Properties

        /// <summary>
        /// 获取任务实例
        /// </summary>
        public static TaskManager Instance {
            get {
                return taskManager;
            }
        }

        /// <summary>
        /// 任务线程列表
        /// </summary>
        public IList<TaskThread> TaskThreads {
            get {
                return new ReadOnlyCollection<TaskThread>(this.taskThreads);
            }
        }


        #endregion

        #region Methods

        /// <summary>
        /// 这里读取所有的计划任务,并对计划任务按照运行时间间隔分组,每组的任务创建一个TaskThread
        /// </summary>
        public void Initialize() {
            this.taskThreads.Clear();
            IRepository<ScheduleTask> _taskRepo = new EFRepository<ScheduleTask>();
            // 按运行时间间隔分组
            var scheduleTasks = _taskRepo.Table.OrderBy(x => x.Seconds).ToList();

            // 按运行时间间隔分组
            foreach (var scheduleTaskGrouped in scheduleTasks.GroupBy(x => x.Seconds)) {
                // 创建一个线程
                var taskThread = new TaskThread { Seconds = scheduleTaskGrouped.Key };
                foreach (var scheduleTask in scheduleTaskGrouped) {
                    var schedTask = new ScheduleTask {
                        Type = scheduleTask.Type,
                        StopOnError = scheduleTask.StopOnError,
                        Seconds = scheduleTask.Seconds,
                        Name = scheduleTask.Name,
                        LastSuccessDate = scheduleTask.LastSuccessDate,
                        LastStartDate = scheduleTask.LastStartDate,
                        LastEndDate = scheduleTask.LastEndDate,
                        Enabled = scheduleTask.Enabled,
                    };
                    var task = new Task(schedTask);
                    taskThread.AddTask(task);
                }
                this.taskThreads.Add(taskThread);
            }

            // 有时一个任务周期可以被设置为几个小时(甚至几天)。
            // 在这种情况下的概率,运行是相当少的(一个应用程序可以被重新启动)
            // 我们应该手动运行没有长时间运行的任务
            var notRunTasks = scheduleTasks

                // 找到“运行周期”30分钟以上的任务
                .Where(x => x.Seconds >= NotRunTasksInterval)
                .Where(x => !x.LastStartDate.HasValue || x.LastStartDate.Value.AddSeconds(x.Seconds) < DateTime.Now)
                .ToList();

            //为不是长时间运行的任务创建一个线程
            if (notRunTasks.Count > 0) {
                var taskThread = new TaskThread {
                    RunOnlyOnce = true,
                    Seconds = 60 * 5
                    //在程序启动以后5分钟开始运行程序
                };
                foreach (var scheduleTask in notRunTasks) {
                    var schedTask = new ScheduleTask {
                        Type = scheduleTask.Type,
                        StopOnError = scheduleTask.StopOnError,
                        Seconds = scheduleTask.Seconds,
                        Name = scheduleTask.Name,
                        LastSuccessDate = scheduleTask.LastSuccessDate,
                        LastStartDate = scheduleTask.LastStartDate,
                        LastEndDate = scheduleTask.LastEndDate,
                        Enabled = scheduleTask.Enabled,
                    };
                    var task = new Task(schedTask);
                    taskThread.AddTask(task);
                }

                this.taskThreads.Add(taskThread);
            }
        }

        public void Start() {
            foreach (var taskThread in this.taskThreads) {
                taskThread.InitTimer();
            }
        }

        public void Stop() {
            foreach (var taskThread in this.taskThreads) {
                taskThread.Dispose();
            }
        }

        #endregion
    }

TaskService:

public class TaskService : ITaskService {
        public ITask CreateTask(string taskType) {
            switch (taskType) {
                case "sendemall":
                    //执行操作
                    string aa = "123";
                    break;
                case "huilv":
                    //
                    string aa1 = "123";
                    break;
                case "qichuang":
                    string aa2 = "123";
                    break;
            }
            return null;
        }
    }

TaskThread:

 public partial class TaskThread : IDisposable {
        private Timer _timer;
        private bool _disposed;
        private readonly Dictionary<string, Task> _tasks;

        internal TaskThread() {
            this._tasks = new Dictionary<string, Task>();
            this.Seconds = 10 * 60;
        }

        private void Run() {
            if (Seconds <= 0)
                return;

            this.StartedUtc = DateTime.UtcNow;
            this.IsRunning = true;
            foreach (Task task in this._tasks.Values) {
                task.Execute();
            }
            this.IsRunning = false;
        }

        private void TimerHandler(object state) {
            this._timer.Change(-1, -1);
            this.Run();
            if (this.RunOnlyOnce) {
                this.Dispose();
            }
            else {
                this._timer.Change(this.Interval, this.Interval);
            }
        }

        /// <summary>
        /// 部署实例
        /// </summary>
        public void Dispose() {
            if ((this._timer != null) && !this._disposed) {
                lock (this) {
                    this._timer.Dispose();
                    this._timer = null;
                    this._disposed = true;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void InitTimer() {
            if (this._timer == null) {
                this._timer = new Timer(new TimerCallback(this.TimerHandler), null, this.Interval, this.Interval);
            }
        }

        /// <summary>
        /// 添加一个任务线程
        /// </summary>
        /// <param name="task"></param>
        public void AddTask(Task task) {
            if (!this._tasks.ContainsKey(task.Name)) {
                this._tasks.Add(task.Name, task);
            }
        }

        /// <summary>
        /// 其中运行任务的间隔秒数
        /// </summary>
        public int Seconds { get; set; }

        public DateTime StartedUtc { get; private set; }

        public bool IsRunning { get; private set; }

        public IList<Task> Tasks {
            get {
                var list = new List<Task>();
                foreach (var task in this._tasks.Values) {
                    list.Add(task);
                }
                return new ReadOnlyCollection<Task>(list);
            }
        }

        public int Interval {
            get {
                return this.Seconds * 1000;
            }
        }

        public bool RunOnlyOnce { get; set; }
    }

很重要的是要在Global中添加:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            TaskManager.Instance.Initialize();
            TaskManager.Instance.Start();
//***
        }
    }

顺便附上数据库:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[ScheduleTask](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [Seconds] [int] NOT NULL,
    [Type] [varchar](500) NOT NULL,
    [Enabled] [bit] NOT NULL,
    [StopOnError] [bit] NOT NULL,
    [CreatDate] [datetime] NOT NULL,
    [LastStartDate] [datetime] NULL,
    [LastEndDate] [datetime] NULL,
    [LastSuccessDate] [datetime] NULL,
 CONSTRAINT [PK_ScheduleTask] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
9    发送邮件    20    sendemall    True    False    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483
10    获取汇率    20    huilv    True    False    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483
11    提醒起床    30    qichuang    True    False    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483    2015-07-03 09:30:51.483

运行结果,可以看到,每20秒的时候,会执行发送邮件跟获取汇率。每30秒会执行提醒起床。

 

Tags: 项目   计划任务   计划   线程   定时器