來到園子有些時間了,一直關注着園子里大牛的帖子。作為一個走人社會2年的猿,也一直幻想着有一天能技術牛一回,升職加薪。春節快到了,老板也不給發年終,尾牙也沒有,連出差的報銷一直卡在老板那一個多月了也沒有結果。這些就都不說了,這些都不是重點。重點是今天是鄙人博客園開篇的日子,分享一下自己的想法,願大家多多指教。
最近,公司正在開發智能家居項目。作為還是一名涉世不深的猿,還沒有什么資質討論架子,這種高大上的問題,就討論一下小功能的實現。
- 場景再現:
在智能家居項目中,經常有定時檢查家居狀態的需求。如:凈水器濾芯過期警告。要求每天12:00檢測,所有再用凈水器濾芯使用時長。如果過期,就向用戶手機推送濾芯過期警告。
- 功能需求
- 在此將每個定時處理動作叫做任務(Task)
- 由於項目中這樣的任務不唯一,要允許多任務定時執行
- 任務可以通過簡單配置便可以運行,配置中指定任務執行內容,定時信息
- 設計思路
- 用兩條線程來實現這樣的功能,1條線程充當調度者(Dispatcher),1條線程充當執行者(Executor)。
- 調度者循環獲取當前執行的任務,交給執行者執行。
- 任務按當前任務執行時間排序,調度者每次調度第一個任務,修改任務最后執行時間,從而修改下次循環任務執行時間。
- 實現
- ITask 接口,任務具體實現需繼承該接口
namespace IHUserService.TaskExecutor { public interface ITask { void Execute(object state); } }
- TaskContent 包含一些任務執行所需的信息
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace IHUserService.TaskExecutor { [Serializable] public partial class TaskContent : IComparable, ICloneable { private string[] partialExecuteTimes; private List<DateTime> tempExecuteTimes = new List<DateTime>(); public DateTime? StartTime { get; set; } public DateTime? LastStartTime { get; set; } public TimeSpan Interval { get; set; } public bool IsRepeat { get; set; } public ITask Task { get; private set; } public DateTime? CurrentExecuteTime { get; set; } public string ExcuteTimeFormat { get; set; } private string assemblyType; public string AssemblyType { get { return assemblyType; } set { assemblyType = value; string[] assembly = value.Split(','); Task = Assembly.Load(assembly[1]).CreateInstance(assembly[0]) as ITask; } } private string excuteTime; public string ExcuteTime { get { return excuteTime; } set { excuteTime = value; partialExecuteTimes = excuteTime.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); } } public int CompareTo(object obj) { var nextTask = obj as TaskContent; DateTime t1 = GetExcuteTime(); DateTime t2 = nextTask.GetExcuteTime(); return t1.CompareTo(t2); } public DateTime GetExcuteTime() { return GetNextExcuteTime(false); } public DateTime GetNextExcuteTime(bool removeTemp = true) { if (StartTime.HasValue && Interval != null) { return LastStartTime.HasValue ? LastStartTime.Value.Add(Interval) : StartTime.Value; } else if (!string.IsNullOrWhiteSpace(ExcuteTimeFormat) && !string.IsNullOrWhiteSpace(ExcuteTime)) { if (tempExecuteTimes.Count == 0) { LastStartTime = LastStartTime.HasValue ? LastStartTime.Value : DateTime.Now; int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0; int incrementYear = 0, incrementMonth = 0, incrementDay = 0, incrementHour = 0, incrementMinute = 0; foreach (var value in partialExecuteTimes) { switch (ExcuteTimeFormat) { case "MMdd hh:mm:ss"://每年的MM月1日 00時 00分 00 秒執行 year = LastStartTime.Value.Year; month = Convert.ToInt32(value.Substring(0, 2)); day = Convert.ToInt32(value.Substring(2, 2)); hour = Convert.ToInt32(value.Substring(5, 2)); minute = Convert.ToInt32(value.Substring(8, 2)); second = Convert.ToInt32(value.Substring(11, 2)); incrementYear = 1; break; case "dd hh:mm:ss": year = LastStartTime.Value.Year; month = LastStartTime.Value.Month; day = Convert.ToInt32(value.Substring(0, 2)); hour = Convert.ToInt32(value.Substring(3, 2)); minute = Convert.ToInt32(value.Substring(6, 2)); second = Convert.ToInt32(value.Substring(9, 2)); incrementMonth = 1; break; case "hh:mm:ss": year = LastStartTime.Value.Year; month = LastStartTime.Value.Month; day = LastStartTime.Value.Day; hour = Convert.ToInt32(value.Substring(0, 2)); minute = Convert.ToInt32(value.Substring(3, 2)); second = Convert.ToInt32(value.Substring(6, 2)); incrementDay = 1; break; case "mm:ss": year = LastStartTime.Value.Year; month = LastStartTime.Value.Month; day = LastStartTime.Value.Day; hour = LastStartTime.Value.Hour; minute = Convert.ToInt32(value.Substring(0, 2)); second = Convert.ToInt32(value.Substring(3, 2)); incrementHour = 1; break; case "ss": year = LastStartTime.Value.Year; month = LastStartTime.Value.Month; day = LastStartTime.Value.Day; hour = LastStartTime.Value.Hour; minute = LastStartTime.Value.Minute; second = Convert.ToInt32(value.Substring(0, 2)); incrementMinute = 1; break; default: throw new Exception("IHUserService.TaskExecutor 配置錯誤!"); } var dt = new DateTime(year, month, day, hour, minute, second); if (LastStartTime.HasValue && dt.CompareTo(LastStartTime.Value) < 1) { dt = dt.AddYears(incrementYear); dt = dt.AddMonths(incrementMonth); dt = dt.AddDays(incrementDay); dt = dt.AddHours(incrementHour); dt = dt.AddMinutes(incrementMinute); } tempExecuteTimes.Add(dt); } } tempExecuteTimes.Sort(); var result = tempExecuteTimes.FirstOrDefault(); if (removeTemp) { tempExecuteTimes.Remove(result); CurrentExecuteTime = result; } return result; } else { throw new Exception("IHUserService.TaskExecutor 配置錯誤!"); } } public object Clone() { return new TaskContent() { AssemblyType = this.AssemblyType, CurrentExecuteTime = this.CurrentExecuteTime, ExcuteTime = this.ExcuteTime, ExcuteTimeFormat = this.ExcuteTimeFormat, Interval = this.Interval, IsRepeat = this.IsRepeat, LastStartTime = this.LastStartTime, partialExecuteTimes = this.partialExecuteTimes, StartTime = this.StartTime, tempExecuteTimes = this.tempExecuteTimes }; } } }
3.TaskCollection
using RestSharp.Serializers; using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Text; namespace IHUserService.TaskExecutor { [Serializable] public class TaskCollection : List<TaskContent> { public TaskContent GetNextTask() 獲取下次執行的任務 { this.Sort(); Current = this[0]; if (!Current.IsRepeat) { this.Remove(Current); } return Current; } public TaskContent Current { get; set; } //當前執行的任務 public void LoadXmlConfigAppandToList(string file) //加載Xml配置 { var xs = new System.Xml.Serialization.XmlSerializer(typeof(TaskCollection)); StreamReader sr = new StreamReader(file); var config = xs.Deserialize(sr) as TaskCollection; sr.Close(); foreach (var item in config) { Add(item); } } public void LoadAppConfigAppandToList() //加載Congfig 配置 { var values = ConfigurationManager.AppSettings["TaskExecutor.TaskCollection"].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); foreach (var str in values) { var settings = ConfigurationManager.AppSettings["TaskExecutor.TaskContent." + str].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var task = new TaskContent() { ExcuteTime = settings[1], AssemblyType = settings[2] + "," + settings[3], ExcuteTimeFormat = settings[0], IsRepeat = true }; Add(task); } } } }
4.TaskCenter
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace IHUserService.TaskExecutor { public class TaskCenter { public TaskCollection Tasks { get; set; } public bool IsRun { get; set; } public void ExecuteTasks()//啟動任務執行器 { Tasks = new TaskCollection(); Tasks.LoadAppConfigAppandToList(); IsRun = true; ThreadPool.QueueUserWorkItem(new WaitCallback(Dispatcher), null); } public void Stop() { IsRun = false; } private void Dispatcher(object state) //處理調度 { var ts0 = new TimeSpan(0, 0, 0, 0, 0); while (IsRun) { Tasks.GetNextTask(); var content = Tasks.Current; var taskTime = content.GetNextExcuteTime(); content.LastStartTime = taskTime; var ts = taskTime - DateTime.Now; if (ts > ts0) { Thread.Sleep(ts); } ThreadPool.QueueUserWorkItem(new WaitCallback(content.Task.Execute), Tasks.Current.Clone());//執行任務 } } } }
5.調用
class Program { static void Main(string[] args) { TaskCenter center = new TaskCenter(); center.ExecuteTasks(); Console.ReadLine(); } } public class TestTask : ITask { string id = Guid.NewGuid().ToString(); public void Execute(object state) { var a = state as TaskContent; Console.WriteLine(id + "本次預定執行時間:" + a.CurrentExecuteTime.Value.ToString("yyyy-MM-dd HH:mm:ss.ffffzzz")); Console.WriteLine(id + "當前系統時間 :" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffffzzz")); } }
Config配置
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <clear/> <add key="TaskExecutor.TaskCollection" value="testtask,0003"/> <add key="TaskExecutor.TaskContent.testtask" value="ss,00|00|00|07|10|30,ConsoleApplication1.TestTask,ConsoleApplication1"/> <add key="TaskExecutor.TaskContent.0003" value="ss,05,ConsoleApplication1.TestTask,ConsoleApplication1"/> </appSettings> </configuration>
