来到园子有些时间了,一直关注着园子里大牛的帖子。作为一个走人社会2年的猿,也一直幻想着有一天能技术牛一回,升职加薪。春节快到了,老板也不给发年终,尾牙也没有,连出差的报销一直卡在老板那一个多月了也没有结果。这些就都不说了,这些都不是重点。重点是今天是鄙人博客园开篇的日子,分享一下自己的想法,愿大家多多指教。
最近,公司正在开发智能家居项目。作为还是一名涉世不深的猿,还没有什么资质讨论架子,这种高大上的问题,就讨论一下小功能的实现。
- 场景再现:
在智能家居项目中,经常有定时检查家居状态的需求。如:净水器滤芯过期警告。要求每天12:00检测,所有再用净水器滤芯使用时长。如果过期,就向用户手机推送滤芯过期警告。
- 功能需求
- 在此将每个定时处理动作叫做任务(Task)
- 由于项目中这样的任务不唯一,要允许多任务定时执行
- 任务可以通过简单配置便可以运行,配置中指定任务执行内容,定时信息
- 设计思路
- 用两条线程来实现这样的功能,1条线程充当调度者(Dispatcher),1条线程充当执行者(Executor)。
- 调度者循环获取当前执行的任务,交给执行者执行。
- 任务按当前任务执行时间排序,调度者每次调度第一个任务,修改任务最后执行时间,从而修改下次循环任务执行时间。
- 实现
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>